From 0215e838e7cdab2b9bf139fa2b9d5228fe38a801 Mon Sep 17 00:00:00 2001 From: Mike Phares Date: Sat, 28 Dec 2024 19:34:09 -0700 Subject: [PATCH] AA.Compare Project to Match not runToDoCollectionFirst Removed Layered AppSettings with Nested Objects at First Level --- .gitignore | 3 +- .vscode/launch.json | 537 +---------------- .vscode/mklink.md | 5 + .vscode/settings.json | 1 + .vscode/tasks.json | 139 ++++- AA.sln | 34 -- Compare/AA.Compare.csproj | 50 ++ Compare/Compare.cs | 168 ++++++ Compare/Models/AppSettings.cs | 78 +++ Compare/Models/CompareSettings.cs | 30 + Compare/Models/Identifier.cs | 32 + Compare/Program.cs | 53 ++ Distance/AA.Distance.csproj | 46 ++ Distance/Models/C_Resize.cs | 3 + Distance/Models/D2_FaceParts.cs | 3 + Distance/Models/DistanceLimits.cs | 59 ++ Distance/Models/E_Distance.cs | 3 + Distance/Models/IDistanceLimits.cs | 17 + .../Models/Stateless/FaceEncodingLogic.cs | 35 ++ Distance/Models/Stateless/FilterLogicA.cs | 93 +++ Distance/Models/Stateless/FilterLogicB.cs | 80 +++ Distance/Models/Stateless/FilterLogicC.cs | 100 ++++ Distance/Models/Stateless/FilterLogicD.cs | 288 +++++++++ Distance/Models/Stateless/MappedLogicA.cs | 193 ++++++ Distance/Models/Stateless/MappedLogicB.cs | 500 ++++++++++++++++ .../Models/Stateless/Methods/IDistance.cs | 57 ++ Face/AA.Face.csproj | 44 ++ Face/Models/D_Face.cs | 3 + Face/Models/Stateless/Face.cs | 53 ++ Face/Models/Stateless/Methods/IFace.cs | 16 + .../AA.FaceRecognitionDotNet.csproj | 51 ++ FaceRecognitionDotNet/DisposableObject.cs | 97 +++ .../Dlib/Python/CnnFaceDetectionModelV1.cs | 111 ++++ .../Dlib/Python/FaceRecognitionModelV1.cs | 129 ++++ .../Dlib/Python/SimpleObjectDetector.cs | 169 ++++++ .../Extensions/FaceDetector.cs | 26 + .../Extensions/FaceLandmarkDetector.cs | 36 ++ FaceRecognitionDotNet/FaceEncoding.cs | 105 ++++ FaceRecognitionDotNet/FaceRecognition.cs | 497 ++++++++++++++++ FaceRecognitionDotNet/FaceRecognitionGroup.cs | 6 + .../FaceRecognitionModels.cs | 22 + FaceRecognitionDotNet/Image.cs | 129 ++++ FaceRecognitionDotNet/ModelParameter.cs | 49 ++ FaceRecognitionDotNet/Point.cs | 107 ++++ Metadata/Models/A_Metadata.cs | 45 +- Metadata/Models/Stateless/Base.cs | 2 +- Metadata/Models/Stateless/Exif.cs | 381 ++++++------ Metadata/Models/Stateless/Face.cs | 11 + Metadata/Models/Stateless/Get.cs | 25 +- .../Models/Stateless/Methods/IMetadata.cs | 16 +- People/AA.People.csproj | 44 ++ People/Models/A2_People.cs | 3 + People/Models/Stateless/Methods/IPeople.cs | 40 ++ People/Models/Stateless/People.cs | 558 ++++++++++++++++++ .../Stateless/ReadOnlyCollectionsLogic.cs | 110 ++++ Rename/Models/AppSettings.cs | 45 +- Rename/Models/Binder/.editorconfig | 2 - Rename/Models/Binder/AppSettings.cs | 108 ---- Rename/Models/RenameConfiguration.cs | 34 -- Rename/Models/RenameSettings.cs | 36 ++ Rename/Program.cs | 6 +- Rename/Rename.cs | 210 +++---- Shared/Models/DistanceSettings.cs | 44 ++ Shared/Models/ExifDirectory.cs | 24 +- Shared/Models/FaceDistance.cs | 53 ++ Shared/Models/FacePartAndFacePointArray.cs | 22 + Shared/Models/FaceRecognitionGroup.cs | 22 + Shared/Models/FileHolder.cs | 3 +- Shared/Models/FilePath.cs | 16 +- Shared/Models/FilePathAndWholePercentages.cs | 21 + Shared/Models/Location.cs | 7 + Shared/Models/LocationContainer.cs | 49 ++ Shared/Models/MappingFromFilterPost.cs | 23 + Shared/Models/MetadataConfiguration.cs | 26 - Shared/Models/MetadataGroup.cs | 22 + Shared/Models/MetadataSettings.cs | 25 + Shared/Models/PeopleSettings.cs | 24 + Shared/Models/PersonBirthday.cs | 21 + Shared/Models/PersonContainer.cs | 48 ++ Shared/Models/PersonDirectory.cs | 21 + .../PersonKeyFormattedAndPersonBirthday.cs | 21 + .../PersonKeyFormattedAndPersonKeyTicks.cs | 21 + Shared/Models/Properties/ICompareSettings.cs | 14 + Shared/Models/Properties/IFaceDistance.cs | 15 + ...ameConfiguration.cs => IRenameSettings.cs} | 3 +- Shared/Models/ReadOnlyCollections.cs | 29 + Shared/Models/ResultConfiguration.cs | 31 - Shared/Models/ResultSettings.cs | 31 + Shared/Models/SaveContainer.cs | 45 ++ Shared/Models/Stateless/IMapLogic.cs | 14 + Shared/Models/Stateless/Id.cs | 32 +- Shared/Models/Stateless/ImageFormat.cs | 24 + Shared/Models/Stateless/Methods/Age.cs | 46 ++ Shared/Models/Stateless/Methods/IAge.cs | 26 + Shared/Models/Stateless/Methods/ICompare.cs | 9 + Shared/Models/Stateless/Methods/IId.cs | 52 +- Shared/Models/Stateless/Methods/ILocation.cs | 25 + Shared/Models/Stateless/Methods/IMapping.cs | 28 + Shared/Models/Stateless/Methods/IPath.cs | 32 +- Shared/Models/Stateless/Methods/IPerson.cs | 55 ++ .../Stateless/Methods/IPersonBirthday.cs | 116 ++++ Shared/Models/Stateless/Methods/IRename.cs | 2 +- Shared/Models/Stateless/Methods/IResult.cs | 64 +- Shared/Models/Stateless/Methods/Location.cs | 78 +++ Shared/Models/Stateless/Methods/Mapping.cs | 61 ++ .../Stateless/Methods/PersonBirthday.cs | 187 ++++++ Shared/Models/Stateless/Mode.cs | 19 + Shared/Models/Stateless/XDate.cs | 18 +- Shared/Models/Stateless/XPath.cs | 36 +- Shared/Models/Stateless/XResult.cs | 68 +-- 110 files changed, 6331 insertions(+), 1275 deletions(-) delete mode 100644 AA.sln create mode 100644 Compare/AA.Compare.csproj create mode 100644 Compare/Compare.cs create mode 100644 Compare/Models/AppSettings.cs create mode 100644 Compare/Models/CompareSettings.cs create mode 100644 Compare/Models/Identifier.cs create mode 100644 Compare/Program.cs create mode 100644 Distance/AA.Distance.csproj create mode 100644 Distance/Models/C_Resize.cs create mode 100644 Distance/Models/D2_FaceParts.cs create mode 100644 Distance/Models/DistanceLimits.cs create mode 100644 Distance/Models/E_Distance.cs create mode 100644 Distance/Models/IDistanceLimits.cs create mode 100644 Distance/Models/Stateless/FaceEncodingLogic.cs create mode 100644 Distance/Models/Stateless/FilterLogicA.cs create mode 100644 Distance/Models/Stateless/FilterLogicB.cs create mode 100644 Distance/Models/Stateless/FilterLogicC.cs create mode 100644 Distance/Models/Stateless/FilterLogicD.cs create mode 100644 Distance/Models/Stateless/MappedLogicA.cs create mode 100644 Distance/Models/Stateless/MappedLogicB.cs create mode 100644 Distance/Models/Stateless/Methods/IDistance.cs create mode 100644 Face/AA.Face.csproj create mode 100644 Face/Models/D_Face.cs create mode 100644 Face/Models/Stateless/Face.cs create mode 100644 Face/Models/Stateless/Methods/IFace.cs create mode 100644 FaceRecognitionDotNet/AA.FaceRecognitionDotNet.csproj create mode 100644 FaceRecognitionDotNet/DisposableObject.cs create mode 100644 FaceRecognitionDotNet/Dlib/Python/CnnFaceDetectionModelV1.cs create mode 100644 FaceRecognitionDotNet/Dlib/Python/FaceRecognitionModelV1.cs create mode 100644 FaceRecognitionDotNet/Dlib/Python/SimpleObjectDetector.cs create mode 100644 FaceRecognitionDotNet/Extensions/FaceDetector.cs create mode 100644 FaceRecognitionDotNet/Extensions/FaceLandmarkDetector.cs create mode 100644 FaceRecognitionDotNet/FaceEncoding.cs create mode 100644 FaceRecognitionDotNet/FaceRecognition.cs create mode 100644 FaceRecognitionDotNet/FaceRecognitionGroup.cs create mode 100644 FaceRecognitionDotNet/FaceRecognitionModels.cs create mode 100644 FaceRecognitionDotNet/Image.cs create mode 100644 FaceRecognitionDotNet/ModelParameter.cs create mode 100644 FaceRecognitionDotNet/Point.cs create mode 100644 People/AA.People.csproj create mode 100644 People/Models/A2_People.cs create mode 100644 People/Models/Stateless/Methods/IPeople.cs create mode 100644 People/Models/Stateless/People.cs create mode 100644 People/Models/Stateless/ReadOnlyCollectionsLogic.cs delete mode 100644 Rename/Models/Binder/.editorconfig delete mode 100644 Rename/Models/Binder/AppSettings.cs delete mode 100644 Rename/Models/RenameConfiguration.cs create mode 100644 Rename/Models/RenameSettings.cs create mode 100644 Shared/Models/DistanceSettings.cs create mode 100644 Shared/Models/FaceDistance.cs create mode 100644 Shared/Models/FacePartAndFacePointArray.cs create mode 100644 Shared/Models/FaceRecognitionGroup.cs create mode 100644 Shared/Models/FilePathAndWholePercentages.cs create mode 100644 Shared/Models/LocationContainer.cs create mode 100644 Shared/Models/MappingFromFilterPost.cs delete mode 100644 Shared/Models/MetadataConfiguration.cs create mode 100644 Shared/Models/MetadataGroup.cs create mode 100644 Shared/Models/MetadataSettings.cs create mode 100644 Shared/Models/PeopleSettings.cs create mode 100644 Shared/Models/PersonBirthday.cs create mode 100644 Shared/Models/PersonContainer.cs create mode 100644 Shared/Models/PersonDirectory.cs create mode 100644 Shared/Models/PersonKeyFormattedAndPersonBirthday.cs create mode 100644 Shared/Models/PersonKeyFormattedAndPersonKeyTicks.cs create mode 100644 Shared/Models/Properties/ICompareSettings.cs create mode 100644 Shared/Models/Properties/IFaceDistance.cs rename Shared/Models/Properties/{IRenameConfiguration.cs => IRenameSettings.cs} (72%) create mode 100644 Shared/Models/ReadOnlyCollections.cs delete mode 100644 Shared/Models/ResultConfiguration.cs create mode 100644 Shared/Models/ResultSettings.cs create mode 100644 Shared/Models/SaveContainer.cs create mode 100644 Shared/Models/Stateless/IMapLogic.cs create mode 100644 Shared/Models/Stateless/ImageFormat.cs create mode 100644 Shared/Models/Stateless/Methods/Age.cs create mode 100644 Shared/Models/Stateless/Methods/IAge.cs create mode 100644 Shared/Models/Stateless/Methods/ICompare.cs create mode 100644 Shared/Models/Stateless/Methods/ILocation.cs create mode 100644 Shared/Models/Stateless/Methods/IMapping.cs create mode 100644 Shared/Models/Stateless/Methods/IPerson.cs create mode 100644 Shared/Models/Stateless/Methods/IPersonBirthday.cs create mode 100644 Shared/Models/Stateless/Methods/Location.cs create mode 100644 Shared/Models/Stateless/Methods/Mapping.cs create mode 100644 Shared/Models/Stateless/Methods/PersonBirthday.cs create mode 100644 Shared/Models/Stateless/Mode.cs diff --git a/.gitignore b/.gitignore index d547c57..42f1e68 100644 --- a/.gitignore +++ b/.gitignore @@ -470,4 +470,5 @@ globalStorage/ Shared/.kanbn .vscode/Har-Files -.vscode/.UserSecrets/* +Rename/.vscode/.UserSecrets/secrets.json +Compare/.vscode/.UserSecrets/secrets.json diff --git a/.vscode/launch.json b/.vscode/launch.json index d54e66b..a40dbc0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,318 +1,11 @@ { "version": "0.2.0", "configurations": [ - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" - }, - { - "name": "Compare", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Compare/bin/Debug/net9.0/win-x64/Compare.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Copy-Distinct", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Copy-Distinct/bin/Debug/net9.0/win-x64/Copy-Distinct.dll", - "args": [], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Duplicate-Search", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Duplicate-Search/bin/Debug/net9.0/win-x64/Duplicate-Search.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Date-Group", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Date-Group/bin/Debug/net9.0/win-x64/Date-Group.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Delete-By-Distinct", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Delete-By-Distinct/bin/Debug/net9.0/win-x64/Delete-By-Distinct.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Delete-By-Relative", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Delete-By-Relative/bin/Debug/net9.0/win-x64/Delete-By-Relative.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Drag-Drop", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Drag-Drop/bin/Debug/net9.0-windows/win-x64/Drag-Drop.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Drag-Drop-Explorer", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Drag-Drop-Explorer/bin/Debug/net9.0-windows/win-x64/Drag-Drop-Explorer.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Drag-Drop-Move", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Drag-Drop-Move/bin/Debug/net9.0-windows/win-x64/Drag-Drop-Move.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Drag-Drop-Set-Property-Item", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Drag-Drop-Set-Property-Item/bin/Debug/net9.0-windows/win-x64/Drag-Drop-Set-Property-Item.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Instance", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Instance/bin/Debug/net9.0/win-x64/Instance.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Mirror-Length", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Mirror-Length/bin/Debug/net9.0/win-x64/Mirror-Length.dll", - "args": [], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Metadata-Query", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Metadata-Query/bin/Debug/net9.0/win-x64/Metadata-Query.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Move-By-Id", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Move-By-Id/bin/Debug/net9.0/win-x64/Move-By-Id.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Not-Copy-Copy", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Not-Copy-Copy/bin/Debug/net9.0/win-x64/Not-Copy-Copy.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Offset-Date-Time-Original", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Offset-Date-Time-Original/bin/Debug/net9.0/win-x64/Offset-Date-Time-Original.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "PrepareForOld", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/PrepareForOld/bin/Debug/net9.0/win-x64/PrepareForOld.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, - { - "name": "Person", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Person/bin/Debug/net9.0/Person.dll", - "args": [ - "s" - ], - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "stopAtEntry": false, - "requireExactSource": false - }, { "name": "Rename", "type": "coreclr", "request": "launch", - "preLaunchTask": "build", + "preLaunchTask": "build-Rename", "program": "${workspaceFolder}/Rename/bin/Debug/net9.0/win-x64/AA.Rename.dll", "args": [ "s" @@ -326,11 +19,11 @@ "requireExactSource": false }, { - "name": "Set-Created-Date", + "name": "Compare", "type": "coreclr", "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Set-Created-Date/bin/Debug/net9.0/win-x64/Set-Created-Date.dll", + "preLaunchTask": "build-Compare", + "program": "${workspaceFolder}/Compare/bin/Debug/net9.0/win-x64/AA.Compare.dll", "args": [ "s" ], @@ -343,224 +36,4 @@ "requireExactSource": false } ] -} -// https://www.amazon.com/photos/ -// "AmazonUsername": "phares36@gmail.com", -// "AmazonPassword": "JlPhgtv@63", -// Keep creating the .json file the same way -// However load all .json files at the beginning -// When looping through all files and file not in collection path&name or date is different from collection value -// Get id in normal fashion -// If id is in collection update collection to new path/name -// If not save and add to collection -// Nicéphore Niépce in 1826 or 182 -// https://dotnet.microsoft.com/en-us/download -// https://git-scm.com/ -// https://code.visualstudio.com/ -// https://github.com/mikepharesjr/VS-Code-Settings -// https://github.com/davisking/dlib-models -// https://github.com/mikepharesjr/View-by-Distance-MKLink-Console -// https://184.103.9.214/login?folder=/home/vscode/Notes&to= -// Notes at 9/18/2022 10:29 PM -// (637987913910140924) 9/14/2022 10:29 PM - 113 *.jpg && 109 *.json -// (637989361172096980) 9/16/2022 02:41 PM - 094 *.jpg && 026 *.json -// All including (637991052364021796) 9/18/2022 1:40 PM - 17435 *.jpg && 16237 *.json = 93.128763980499% with int match only -// List of people with current person key formatted in tab form -// Exclusion list for Robert -// Person birthday allow # -// Host face directory on Ubuntu -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/49158951_2264915190186558_112485584324263936_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=84a396&_nc_ohc=Q78znguHVeMAX88OKg9&_nc_ht=scontent-lax3-2.xx&oh=00_AT_gJFk9xytYuXp9p5smDm0mMDXzsPnFS_EfKEbiL3KYqg&oe=63523F37 -// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/290625560_10217841255422109_3297482250419620718_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=730e14&_nc_ohc=O3ybqwDLK9AAX_V0FGP&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT-9rtBghXU7lAGkOy4KPHbV9Kyj8DGm-BGtUabWgQJbOg&oe=63335209 -// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/243093116_10216723829287154_8982585928017225043_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=730e14&_nc_ohc=TW6UDC8v9egAX-_YcWf&_nc_ht=scontent-lax3-1.xx&oh=00_AT8rE2JOfkJINSsA5imGVBDbXNDvxx1_uZn0sLM-6ITDUw&oe=633238A9 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/281249_1450445399251_3598212_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=ihKT3xtvWXoAX-kiat-&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT9cmbHu00fc0-aR-k6MP7T_cja6geiAkobbvU4pdfG6KQ&oe=63527F21 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/62557_157063120990498_3918482_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=de6eea&_nc_ohc=lV8bmtAes-0AX8Uk9pG&_nc_ht=scontent-lax3-1.xx&oh=00_AT-ieBL8wIZ4gQmRWAvTWDu9wBzAsPV5s4c82MvOs6oU5g&oe=6355846B -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/166577_1634651034600_6777460_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=nEZ0RR0cqn4AX-wA-VH&_nc_ht=scontent-lax3-1.xx&oh=00_AT_ig2HUMDQl1fhXY0Inm0QWC3i2__rPUAWYF2Wqx-pkew&oe=635290D6 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/97107804_10223189072017432_5088375698351980544_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=t0rqlgAAd_0AX-ANM1H&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT_hqG2piNkASRB5z94qeY8q7hy90ku9Dka4enL4wsAnTg&oe=6354CD8A -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/121491463_10159438417238072_1207001349879930424_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=rZTiRaFTrCkAX_L4sVS&_nc_ht=scontent-lax3-2.xx&oh=00_AT_uKD2jeNUTwlyN5r6UXRK40aNNgRNCZrrJ9B-WW6tlag&oe=63522946 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/96370874_10158521458082578_8748970895894118400_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=qvIn4gTjvo0AX-dHnuq&_nc_ht=scontent-lax3-1.xx&oh=00_AT8R-D8AsjWKMlUxK-AimPNzxuNT06zQCS43ESdf1LEeoQ&oe=63562F54 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/96681856_10157495047223435_2857053180632498176_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=UwF0qUGoG9QAX-S1LcI&_nc_ht=scontent-lax3-2.xx&oh=00_AT-5j0EiZB6HOOc2jGqGOll2zFoNTdDLKLfJztq0HYWagg&oe=6355A2A9 -// https://scontent-lax3-1.xx.fbcdn.net/v/t31.18172-8/26850365_1810492538961485_3240264679723665015_o.jpg?_nc_cat=110&ccb=1-7&_nc_sid=0debeb&_nc_ohc=WrJYsgu1eS4AX8kIsyM&_nc_ht=scontent-lax3-1.xx&oh=00_AT-fzFN40kYFjF2-uYL6KcgKPxgFkDZ5BcyIUVFdCTf2tg&oe=635485B7 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/67735718_10219770387799941_2254439277247070208_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=730e14&_nc_ohc=MD8haDYY7uIAX_dwTY4&_nc_ht=scontent-lax3-1.xx&oh=00_AT_zhJpjbWomrihWiSq35KFkmpBfEgbXCSNM68icaABxoA&oe=6355871D -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/59983603_2215207855225638_2271200674882519040_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=-2zmPWdcy9IAX8KXJ_t&_nc_ht=scontent-lax3-2.xx&oh=00_AT_7N0yufGym-cuQfeMK6iceBNI7eEyaScRm3m3_4njTIQ&oe=6354C2B6 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/204501292_10222856568897395_8588510782161393042_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=2gLdnkn19swAX_9mAG1&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT_ha5ncAI7ykhbgFNT1_4NWCvPGFWQLgdq6q20nZSIVQA&oe=63537D09 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/65945099_10217135244827869_3400689284598988800_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=NHnhR9_7UTkAX8wpbxK&_nc_ht=scontent-lax3-1.xx&oh=00_AT_-Q-zUeDoD11B9TY_n_MWEimMiXCokB_5TojplmU6_Eg&oe=6355A2B8 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/22055_581856018827_4131263_n.jpg?_nc_cat=107&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=zsqIe2xzpf0AX8lcyLV&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT_DzvUir_30cauAjp8eaSlYKVADdanzWo1NAQK7_b3M-w&oe=63548E83 -// https://scontent-lax3-1.xx.fbcdn.net/v/t31.18172-8/14525108_10202312892739292_8313619716862825541_o.jpg?_nc_cat=102&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=lZ4MpT6i_MIAX-3d-nV&_nc_ht=scontent-lax3-1.xx&oh=00_AT_9JoZdMdlNV71PdrkU7AmvFkkwqllEg9sVTvcGyzoESw&oe=6353993A -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/104325415_860091804515928_5960365004832843376_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=174925&_nc_ohc=y1AW-ld80OcAX_927wW&_nc_oc=AQluU8Pwuqxs9xBVYhQJ9ThWOh5vLIKvQdOQnCtZpN2-j-6d9PRMHWHR5aaFBkcJicA&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT8vQwiLvz7cJU61FkIPLtcc8j8tlL2tC6zRlH1i20VUmQ&oe=6356EACE -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/43565411_10217177505092918_6185540976904241152_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=730e14&_nc_ohc=yBls9BCXhdwAX_doX-2&_nc_ht=scontent-lax3-1.xx&oh=00_AT-PYyffphSxOeGM_3aC4oHSA4U9cEjm2OrappZ_jh6qIg&oe=6355895C -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/48422692_10157144542022625_3324340889383337984_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=h_gtXGa5nSAAX-CB2E1&_nc_ht=scontent-lax3-1.xx&oh=00_AT8x7vZA5VN27hfoMPcQ39So-1COUnuNjDj4iz8d-cw6wQ&oe=6354B370 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/150153899_10208532293942419_1808101907825622971_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=3NZWKCpJ0VYAX911uCo&_nc_ht=scontent-lax3-2.xx&oh=00_AT_NhKwagHEsPuQVSERZNQ2_6MOIG2UKG5r32c0fIPaeQA&oe=63552624 -// https://scontent-lax3-2.xx.fbcdn.net/v/t31.18172-8/13641143_10209202266801205_7525162492297688727_o.jpg?_nc_cat=106&ccb=1-7&_nc_sid=174925&_nc_ohc=AETs7fkfLacAX-q9cnO&_nc_ht=scontent-lax3-2.xx&oh=00_AT89DOYAP9GbNvEgmlI0z9VK9Z8uJttFQyuO-tfe5OGbFQ&oe=6355CF5D -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/299875_2023116112081_1500929171_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=bsY9uckSAQwAX-xxGaU&_nc_ht=scontent-lax3-2.xx&oh=00_AT8OStQmDU_7NLNrYLeNfoULcBA-onU1V3Z4bn35-ipzCQ&oe=63555339 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/1544444_10203587296855592_4576811169900508598_n.jpg?_nc_cat=100&ccb=1-7&_nc_sid=730e14&_nc_ohc=pYUGNkoYGPcAX8X2gtw&_nc_ht=scontent-lax3-2.xx&oh=00_AT_thzQZyWPWDA8t1acXqEzifLMXxcPTUOgqWqmeuk3SfQ&oe=63566033 -// https://scontent-lax3-2.xx.fbcdn.net/v/t31.18172-8/10887668_10205175511939476_7644367668075304275_o.jpg?_nc_cat=100&ccb=1-7&_nc_sid=730e14&_nc_ohc=2T_utQvqkXYAX94o-WV&_nc_ht=scontent-lax3-2.xx&oh=00_AT8aSCQ4JVgRULd1RxRJ8DYqPtB9EyGY-May2QwwqKSqjQ&oe=6356B098 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/78675669_10102984650797787_175454688261439488_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=7Plv_RW77xoAX9PiH9G&_nc_ht=scontent-lax3-2.xx&oh=00_AT-npzVNkTzAdYgQ4D1ltfyl8llC_xqOHJ9Mi8Vmh2khlw&oe=63571251 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/59569741_10214142910059142_9219208491263066112_n.jpg?_nc_cat=100&ccb=1-7&_nc_sid=730e14&_nc_ohc=F7GF2uCFljcAX8Skffr&_nc_ht=scontent-lax3-2.xx&oh=00_AT9e3f8x6hSmAcxQ5JDUvDPFEg0wcecKw175Qfj_WMwADQ&oe=635488AA -// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/290559432_10209959340897099_842876274165104077_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=Vgxc93JsPBEAX8zrKaq&_nc_ht=scontent-lax3-2.xx&oh=00_AT-pPGd4o-t7kSeY4XKOTDeOzfg6wXOMBdY6rldQyrKQmw&oe=6335398E -// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/308587069_10210153867920153_6202104893656263114_n.jpg?stp=cp6_dst-jpg&_nc_cat=104&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=SKB2FSdXFQEAX_v1t5g&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT_E1y9uQ27O5_FAyAeRJ0XotEyBEXytkM2iDCGvldSxLw&oe=633549B9 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/67309987_10214298629206292_6820175905885782016_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=730e14&_nc_ohc=BTQEZF_dwRwAX-cxNEm&_nc_ht=scontent-lax3-2.xx&oh=00_AT8_z-JOBPmskVsJ0S9bOYgcYngpVrMjhVrfFk3r06Wj8Q&oe=635473AE -// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/263710427_1324163581376203_7502850514450466967_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=e3f864&_nc_ohc=2KB9VISs3y0AX-PiYUd&_nc_ht=scontent-lax3-1.xx&oh=00_AT_WaHBBSAR2-1dDEdpufugFf4eBttyGfIIflCwvCu_76g&oe=6334DBC3 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/67181620_114720179834180_3651229536021905408_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=CYF8Qyy506MAX-GBXV_&_nc_ht=scontent-lax3-2.xx&oh=00_AT_dFZiY42lloSfdiqNgSBlDpS9eGIEu85hhG5KIQD7DwA&oe=6354B320 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/68383564_10217654576270105_5209606295652401152_n.jpg?_nc_cat=109&ccb=1-7&_nc_sid=730e14&_nc_ohc=phqcunpNs5EAX-d33kB&_nc_ht=scontent-lax3-1.xx&oh=00_AT9n5kFQnIPSXUzNAMb19Wpue1YA-bHFpSCE-_tOXD8Gnw&oe=635615C2 -// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/285466259_7423531907720135_1229107015631332741_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=z85uLRC706sAX_H3_Ht&_nc_ht=scontent-lax3-2.xx&oh=00_AT-_wJpeYhqcdivdF4SbBO3B6M082qGD3qdGAPDmVP6nAA&oe=6334B2ED -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/32919393_10214483292069982_3789509822047584256_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=oJzHahluIDYAX-5-UGl&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT9e7dHUoseqwf6qF39NHEnPzal6NzQgyCwio6SvzUZTUQ&oe=63575682 -// https://scontent-lax3-1.xx.fbcdn.net/v/t31.18172-8/22256636_1931521070438917_6414682443902494017_o.jpg?_nc_cat=102&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=bf99qSpRJOQAX_stzg5&_nc_ht=scontent-lax3-1.xx&oh=00_AT-eoWkn2Gaqj8BGasgNOycRzf3AiQE2GqHtKC28_-vFug&oe=63586C84 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/600876_10151927436713567_876993105_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=dcNCC6RNOooAX985ENZ&_nc_ht=scontent-lax3-1.xx&oh=00_AT-9Q999B_sWiS-yPP48gVdrheH0bY-DAiOKmkExbnW5Rw&oe=6355ED8A -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/35235266_10213962597433782_6655457381733892096_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=uxfDliBhPHUAX_JXJNU&_nc_ht=scontent-lax3-1.xx&oh=00_AT9zGc-8QqaAHItoDqGzKAMEYOU0fEQz_ld1R_CGxPokug&oe=63565840 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/161932468_10159007128985149_3980438021322511452_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=6dCTPFwvFPcAX8ogdv8&_nc_ht=scontent-lax3-1.xx&oh=00_AT8pspicXFXgj-MOKzkTyp_ZpuboruYwyM-MMNwro_m9JA&oe=63564482 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/91250196_10215323899906785_1298078522899693568_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=730e14&_nc_ohc=CSXhKF_1hqsAX9qlbvP&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT-JZSnju_3XqWV6w9rSSUmuo2oIaCig5pn7JmrvFbBfGw&oe=6354B339 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/428510_490436967638912_1438526428_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=ba80b0&_nc_ohc=s0ZgvdtWwOsAX87AD-I&_nc_ht=scontent-lax3-1.xx&oh=00_AT-gv7QKuJhEjRPbmQr5hLG163-EQXcx6FJ8aHCcPTfEXw&oe=63563591 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/50312617_10161338488075048_5525340242208358400_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=NcQcK_z6t9kAX9-On7t&_nc_ht=scontent-lax3-2.xx&oh=00_AT_WYrFNqGwzuSssGPBRmhpqGlyf1k2ulbdKakrs4xAHBg&oe=635832DB -// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/247966826_10200232179385908_1907223790607385605_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=Xn0j5rxLaCQAX8OD7k4&_nc_ht=scontent-lax3-2.xx&oh=00_AT_bVVWAuaGYpIcBOCbAtn_xyS2Rqu--uKs06-FKOfjuzg&oe=633583B8 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/57840024_10161651547530207_1744748790901899264_n.jpg?_nc_cat=107&ccb=1-7&_nc_sid=730e14&_nc_ohc=TKg3rTiXkYUAX-V8NO5&_nc_ht=scontent-lax3-2.xx&oh=00_AT-Mepp0iN8-mHhyKRHj-R5yBOu2wZGtQeApkSoZTzM3mg&oe=6356A1D2 -// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/269876615_612483726673361_2182050585964176784_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=174925&_nc_ohc=9QJt0X_FDlEAX_Okew3&_nc_ht=scontent-lax3-2.xx&oh=00_AT_Yq8OflcYUA39JsmM__UxFYi8YASqsrRYx-T1Qn2NPSw&oe=6336391B -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/126332086_10224303272989365_2242003924983097751_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=zpX0nadZ9XMAX8m1Kk1&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT9c_dq-TLiNxgeqmp9Pwnqp9xjUIG3_ijxmiAW4MPYBKw&oe=63578DD6 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/74624149_10215957575592842_7757392320252608512_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=22WGGAPeTdAAX87greQ&_nc_ht=scontent-lax3-2.xx&oh=00_AT9J2qV9zBMEPuqsibA-87pjTBg3e3hVwHb211GO7lJicQ&oe=6355F050 -// https://scontent-lax3-1.xx.fbcdn.net/v/t31.18172-8/10649011_812346452173040_8035951943727654673_o.jpg?_nc_cat=104&ccb=1-7&_nc_sid=84a396&_nc_ohc=aDP-7N5XOgMAX9eflUn&_nc_ht=scontent-lax3-1.xx&oh=00_AT_mCAWB-OTJ32F1W90m-bPWu9GmjvFr-j1VdAQmkdNtXg&oe=63565628 -// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/289655024_10228170790084748_4126077972169573791_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=NeX9XD5pcwMAX9UBHLe&_nc_ht=scontent-lax3-2.xx&oh=00_AT-Qh77ATMIGL6JnKHr9rIim3Ui_UU7XH_S02S7-iu21qg&oe=633700F0 -// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/266998682_10227151119593623_456420668145746211_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=vjNb4pBYn5YAX_gWfIx&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT_rv-lqoI2u7Y-FMU8G8Vfxo6QyA-tTEIMKRpRx9TxZPg&oe=63353CCC -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/40093120_10217172129645111_2951374557987995648_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=X1j0ykpT26oAX8kDzfQ&_nc_ht=scontent-lax3-1.xx&oh=00_AT-jst6eXb7F4NsSJRj4jOQvKqQOiiMP4K5iT8xq2t4dTg&oe=63588214 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/31870362_10216285257993874_8330563136197230592_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=84a396&_nc_ohc=8kJ5pbwsKDMAX9hIVFA&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT9vRjGUZdT7KasJ4Ndti2LHfrPaTcahl4ipT9BmdRZJeg&oe=6356737F -// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/230074448_10103880528811228_6909448863476427135_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=0debeb&_nc_ohc=cUpp1Y_DO8MAX92tN_t&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT-K2_yEERUmvvjkjUKf7zwaV2Ofq1fHIQAtEkCt7I2Bhw&oe=63352B95 -// https://scontent-lax3-2.xx.fbcdn.net/v/t31.18172-8/13502600_10210212049485607_95598559411189791_o.jpg?_nc_cat=101&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=AEojM0a2X5cAX8kmHfZ&_nc_ht=scontent-lax3-2.xx&oh=00_AT-ZX7vIfwazh_zcDpFCLxwLgp90gERyBG2CtGxIClEnYA&oe=6355A9C7 -// https://scontent-lax3-2.xx.fbcdn.net/v/t31.18172-8/14195939_1314130471931770_9026437168267009241_o.jpg?_nc_cat=106&ccb=1-7&_nc_sid=730e14&_nc_ohc=f8owaBX7kMQAX8ioj3m&_nc_ht=scontent-lax3-2.xx&oh=00_AT8N8GR4GXmKa3w_dP952fl1_bx_s7CTz8qFtxaBK91S2w&oe=6355DBA1 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/67428022_1287438048070254_7628190366730027008_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=b9115d&_nc_ohc=wO9dTzPDXjcAX85VZFY&_nc_ht=scontent-lax3-1.xx&oh=00_AT9ZDKZuwlVncwGAKb6fkty_AaqnBNmKj46n2L3V1Y_V5w&oe=63589442 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/95959209_10221111135660559_6413640771529867264_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=730e14&_nc_ohc=tN-1cmyDpEsAX-GecHc&_nc_ht=scontent-lax3-2.xx&oh=00_AT9w87N6Gq277785G3f6iF5IhEtBtJ7FTZwxkK5dipNqOw&oe=63575F14 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/207486_1959635958499_5198863_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=2c4854&_nc_ohc=Z2KnsIT4a60AX9wPnkk&_nc_ht=scontent-lax3-2.xx&oh=00_AT8Vxh2vW53SBZ4sH9cgZEofWKOUnpf8fpMjQRjZGCR6oQ&oe=6355FB36 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/37219804_10213591073105306_9108104030982242304_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=pngJAV_sIesAX8zSJG-&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT8UXpCCDSQ8uOPtSrdzcty0tSpO6TCEKCL-K7q-BMb5CA&oe=6357898C -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/427539_3441473049618_710901137_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=3DPj0_PEbSsAX9ryGz1&_nc_ht=scontent-lax3-2.xx&oh=00_AT9t82wuMvW2xdHA8E1VokNBzIhHtgBbpH8fvrIxE_0rPQ&oe=6357DCF0 -// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/241548211_237343028191419_6995991527624737723_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=nsC7CBQxaO8AX-De2g8&_nc_ht=scontent-lax3-1.xx&oh=00_AT_V2gvedewiVuhPIozJyq4ccKm6WoxBkXe0Ocm1fTZ3wA&oe=63365FAA -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/10253934_10152334525164461_3158331570278601806_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=ba80b0&_nc_ohc=0A0Haq5W-IgAX8Y28oA&_nc_ht=scontent-lax3-1.xx&oh=00_AT-3tqlxtQj1n0D5t1jzVfQi22q3OMyQQrBcw2ZBrmYGaA&oe=6356ACAB -// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/308817118_10218508885627876_6441766117990882318_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=730e14&_nc_ohc=1kMU8trJLw4AX_R76M2&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT9k53CcIY7KnW3CjRR4bbE8wjuAUVVi6D1an-kcbOaF2g&oe=63363C9E -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/41038997_10156771390897533_9196241072043327488_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=174925&_nc_ohc=CK2PqvoqhVsAX9xGRSK&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT8qHnIW7OsQMRb7htRwC7byyqcK4X5qAkdz8IM4caO_iA&oe=6357DA17 -// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/279316302_4583494791751734_5208076832025426501_n.jpg?_nc_cat=109&ccb=1-7&_nc_sid=e3f864&_nc_ohc=Mt2sOGHVzbMAX-ghCmr&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT_xdFzERKvZfApmuZ19H39uWsTz0vrJR7fivTuaLGiE1g&oe=63372D6C -// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/213757548_10222718701187533_9216326465615113019_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=bMlCbM1DFccAX-UEjOE&_nc_oc=AQlwSf-Xrfqx-ldXQ8cz-gImrOZnTfVoNReVucU0i3C--RLTsfldVzKT8ij7sn7Jn3E&_nc_ht=scontent-lax3-2.xx&oh=00_AT-YAQ9scfYOoaPy5Kzb6yWcpe4t5MNTfE1pAP-RD3oBAA&oe=63379E32 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/1005197_172610252919953_1797035311_n.jpg?_nc_cat=102&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=9wV_ZoGuDfQAX9ZQWRM&_nc_ht=scontent-lax3-1.xx&oh=00_AT_M3_rgENbCEjmouX8Ar_N_s-P5Tke0TvPzza0Mwx90dg&oe=635764FD -// https://scontent-lax3-1.xx.fbcdn.net/v/t31.18172-8/24130218_2001836833160114_7527164989319060274_o.jpg?_nc_cat=104&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=ZxM5zjih3tYAX8IsXx9&_nc_ht=scontent-lax3-1.xx&oh=00_AT-G2-I-OXpcoq9TKqn_YT3gObzj8GMavpJe-KH7xj_5Lg&oe=6357091E -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/11745695_10206235519080812_1953505210138379535_n.jpg?_nc_cat=109&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=RDqIQjOVB0AAX8Q90_Z&_nc_ht=scontent-lax3-1.xx&oh=00_AT_FogaOVWhgLcesEzaidYx--sKeBVCD6xDg2IWJdvcafA&oe=6356095A -// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/280118285_2094899397354077_5352291217200307805_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=YQDlNI2xTrIAX-curq5&_nc_ht=scontent-lax3-2.xx&oh=00_AT8SuwF2W7tioXfAfCCun6zQsrIUjArOSr0ZlRb3F_Ej2Q&oe=63378827 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/13233144_10101471840461827_9039193337970955304_n.jpg?_nc_cat=107&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=aiRRaotqsnYAX-slwLd&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT_CxgSXV-0uNiiz_jLV_s3cPAk2vUyoro5MVsXsVyiEcg&oe=6358E40F -// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/305309404_10220461241674046_1323434462733520520_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=NajFsslaLvcAX8-Kuz_&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT89eIRz0st3qjPnhURiW9W7wXbaOV0usVykQiKRL_TRhg&oe=6337A145 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/32481784_10216908834904287_1754862128918953984_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=8mU2Cf_iAFwAX80s1xj&_nc_ht=scontent-lax3-2.xx&oh=00_AT8SXOmaqoNTZvBik5j4hE7YEenshkhPggwNgfLdIjIn4g&oe=6355E4D5 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/22814020_10211731412395324_9169598565701382472_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=UTLq0qx2PQwAX-Y9rIg&_nc_ht=scontent-lax3-2.xx&oh=00_AT_T9Bqd0LwRhuUkbMuUHQcCxcB7I3TAumrw7BR-UuEe3Q&oe=63563325 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/94927050_10220780432954871_7311541287197343744_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=qJofGIany9YAX_15Uhx&_nc_ht=scontent-lax3-1.xx&oh=00_AT_K9V-fQ-IOdVE3BtUQCtFvljScg3xdbjWK8LWh0fs3EQ&oe=6355F4D6 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/47685020_2061588197234140_3115001451777097728_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=g2_slmBopOEAX-kvLeT&_nc_ht=scontent-lax3-2.xx&oh=00_AT9CXSXrom34MSI2zgqXx7qdPKAn8fhKG8dBqGE3l1c_Nw&oe=63567CA6 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/39221047_10156127305716865_6824896341232058368_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=osdlvseA5vkAX9zsbYp&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT96aJo9mM_AXgkaQau0AYQ6GgMk3m02z4fpuT1FwYvXtw&oe=6357B649 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/11037634_10204752644508765_3331659968007967889_n.jpg?_nc_cat=107&ccb=1-7&_nc_sid=730e14&_nc_ohc=mjDY6IkATEoAX_WhE62&_nc_ht=scontent-lax3-2.xx&oh=00_AT8gl0GngVzWAynqGAC8zrgZUqAEgahMmzE5X41KpPnYxQ&oe=635825BA -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/36371667_10213980051274137_6162910755566911488_n.jpg?_nc_cat=102&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=-j6ofEojPiIAX-yLrVV&_nc_ht=scontent-lax3-1.xx&oh=00_AT9ZvpazeIfHclhHr24SguNiu0m4WMAj2k5jm2I40JIv5w&oe=635612A1 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/13938350_626048368466_6216870049337020892_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=AbgIJkB2bH4AX-TuAoA&_nc_ht=scontent-lax3-2.xx&oh=00_AT-o0wi5Cz5vl1WDgcDm2BneTIBTuR-__qxxmR-3Fgyg_w&oe=63579473 -// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/306356815_5223195104456342_2659949811667496237_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=730e14&_nc_ohc=NQdsObWTav0AX9EbW12&_nc_ht=scontent-lax3-1.xx&oh=00_AT8v-jsilYOUGKvQdlt7PXQmbdJRfZD0-GdycokWq-aNwg&oe=63365BAB -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/44500772_10217404839095591_8903105516822069248_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=RqwiyNoil04AX_Y4FGb&_nc_ht=scontent-lax3-1.xx&oh=00_AT85t0AjyzMXSj_UcUhKqWf7ZXdQOFAy12aFdeOO9cjBYg&oe=6356ED81 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/23659202_10212567297792300_7982751930141302131_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=ad2b24&_nc_ohc=9vxjTRupUuAAX8fJ3Qa&_nc_ht=scontent-lax3-2.xx&oh=00_AT8wFIaUMmI2_kLSejxewgzzMK6g0ngEJVfhEQukM0zQdQ&oe=63596B35 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/92554157_10158297246718523_986365937043111936_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=lWhPQlbtKVoAX-4D9Ke&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT_P1suJEkfnHlSL3rPoDoYW2exSg_gQyN--HoylQbt_JQ&oe=63567442 -// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/280689051_10209840036394561_8737214584492733274_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=iMAAHqNekCgAX8fvEAm&_nc_ht=scontent-lax3-2.xx&oh=00_AT8b7_ODsDN7lNgTKr7JZiUE65R7WWYxENl1vOUGeobpBw&oe=6336E678 -// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/25110_107749419250772_1842086_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=xPyRCvImlaoAX9Z_UTs&_nc_ht=scontent-lax3-1.xx&oh=00_AT85xffhHDJlhzyFTBvWs8j3Gd4GyDCte_Pnd7hNM006sw&oe=635880C2 -// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/10259940_10153561109653458_6837681277740526675_n.jpg?_nc_cat=100&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=T1v9Wfqz8rUAX8y6bxq&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT9Y9j5s19n0EzgO_dZMom8tAweFYIrJLOEsrsr4HAWHkQ&oe=63588CAE -// https://scontent-lax3-1.xx.fbcdn.net/v/t31.18172-8/17621858_10154253751886426_3939148233753829194_o.jpg?_nc_cat=104&ccb=1-7&_nc_sid=ad2b24&_nc_ohc=yMOqJQQBV80AX8Xlzr-&_nc_ht=scontent-lax3-1.xx&oh=00_AT_z0oeHJ0fqKhVA4WdgGE2cZSgL0bIKNXzPsY0zeLipAg&oe=6356AF4C -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// FileSizeChanged -// LastWriteTimeChanged -// LastWriteTimeChanged -// LastWriteTimeChanged -// LastWriteTimeChanged -// LastWriteTimeChanged -// LastWriteTimeChanged -// LastWriteTimeChanged -// LastWriteTimeChanged -// LastWriteTimeChanged -// LastWriteTimeChanged -// LastWriteTimeChanged -// - Device Videos 2_0_0_3 - Current - Kristy Google -// - Device Videos 2_0_0_3 - Current - Mike Google -// - Device Videos 2_0_0_3 - Current - Tracy -// Chester & Linores ?Pearson? -// Distance face files date time? -// If not Hog and Original image size resize face -// New json file with known locations at original image size -// Add person to metadata when saving to SaveFilteredOriginalImagesFromJLinksForOutputResolutions maybe SaveMappedForOutputResolutions -// GetFaceLocations -// Stream stream; -// FileType fileType; -// const string abd = ".abd"; -// List jsonGroupFiles = GetFileCollection(jsonGroupFileCollection); -// foreach (string jsonGroupFile in jsonGroupFiles) -// File.Move(jsonGroupFile, string.Concat(jsonGroupFile, abd)); -// stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read); -// fileType = FileTypeDetector.DetectFileType(stream); -// stream.Close(); -// stream.Dispose(); -// if (fileType == FileType.Unknown) -// continue; -// if (!isUniqueFileName || string.IsNullOrEmpty(resultAllInOneKey)) -// { -// if (!isUniqueFileName && !string.IsNullOrEmpty(resultAllInOneKey)) -// File.Move(Path.Combine(resultAllInOneKey, fileName), Path.Combine(resultAllInOneKey, $"{fileName}.del")); -// resultAllInOneKey = string.Concat(jsonGroupDirectory, directoryName[length..]); -// } -// if (!jsonGroupFileCollection.TryGetValue(resultAllInOneKey, out keyValuePairs) || !keyValuePairs.TryGetValue(fileName, out collection)) -// fileAndJsonGroupFiles.Add(new FileAndJsonGroupFile(file, isUniqueFileName, Path.Combine(resultAllInOneKey, fileName), false)); -// else -// { -// foreach (string jsonGroupFile in collection) -// // File.Move(string.Concat(jsonGroupFile, abd), jsonGroupFile); -// fileAndJsonGroupFiles.Add(new FileAndJsonGroupFile(file, isUniqueFileName, jsonGroupFile, true)); -// } -// C:\Program Files\ImageMagick-7.1.0-Q16-HDRI>identify -format '%Q' "D:\7-Question\- - - Images\Mike Goolgle Photos 2014\-1507651551.jpg" -// '98' -// C:\Program Files\ImageMagick-7.1.0-Q16-HDRI>identify -format '%Q' "D:\7) Question\- - - Images\Mike Goolgle Photos 2014\197616258.jpg" -// '90' -// dotnet dead-csharp --inputs "**/*.cs" --excludes "**/obj/**" "**/I*.cs" "**/UnitTest*.cs" -// " - Shortcut (2)" -// _ Manual Copy Successful -// # Don't know full birthday -// ] Match -// ^ Know birthday -// _ Import of Partial info -// ` Want to find -// ~ Approxomite age -// + Approxomite age and have at least one image -// XPAuthor = 40093 | [3-1] ("Picasa") -// Artist = 315 | [182-3] ("Picasa", "unknown", " ") -// Kristy, Mike, Tracy, Mackenzie, Julie, Patrick, ... -// XPKeywords = 40094 | Vulgar !9, Blurry !4, Duplicate !3, Irrelevant !2, Review !1, Slideshow [0] -// XPSubject = 40095 | Family Pictures, Scanned [2-1] (" ") -// XPComment = 40092 | [9-2] ("\uFFFD", " ") -// Rating = 18246 | [52-1] ("0") -// UserComment = 37510 | [3570-^^^] -// XPTitle = 40091 | [24-24] ("Logan and his Doggie", ...) -// ImageDescription = 270 | [2135-5^] 4+37+82+1354+647+11 ("Camera 360", "OLYMPUS DIGITAL CAMERA ", "SAMSUNG ", " ", " ") -// int artist = (int)IExif.Tags.Artist; -// int fileSource = (int)IExif.Tags.FileSource; -// int userComment = (int)IExif.Tags.UserComment; -// 0x9c9b XPTitle int8u IFD0 (tags 0x9c9b-0x9c9f are used by Windows Explorer; special characters in these values are converted to UTF-8 by default, or Windows Latin1 with the -L option. XPTitle is ignored by Windows Explorer if ImageDescription exists) -// 0x9c9c XPComment int8u IFD0 -// 0x9c9d XPAuthor int8u IFD0 (ignored by Windows Explorer if Artist exists) -// 0x9c9e XPKeywords int8u IFD0 -// 0x9c9f XPSubject int8u IFD0 -// 0xa300 FileSource undef ExifIFD 1 = Film Scanner -// 2 = Reflection Print Scanner -// 3 = Digital Camera -// "\x03\x00\x00\x00" = Sigma Digital Camera -// 0x9286 UserComment undef ExifIFD -// 0x013b Artist string IFD0 (becomes a list-type tag when the MWG module is loaded) +} \ No newline at end of file diff --git a/.vscode/mklink.md b/.vscode/mklink.md index fad8394..d72b5c6 100644 --- a/.vscode/mklink.md +++ b/.vscode/mklink.md @@ -17,3 +17,8 @@ mklink /J "L:\Git\AA\Shared\.kanbn" "D:\5-Other-Small\Kanban\View-by-Distance" ```bash Sat Aug 17 2024 11:09:42 GMT-0700 (Mountain Standard Time) mklink /J "L:\Git\AA\.vscode\.UserSecrets" "C:\Users\mikep\AppData\Roaming\Microsoft\UserSecrets\fa0fa59b-afe4-4960-9afc-18fcbc7fb41b" ``` + +```bash 1735322893065 = 638709196930650000 = Fri Dec 27 2024 11:08:12 GMT-0700 (Mountain Standard Time) +mklink /J "L:\Git\AA\Rename\.vscode\.UserSecrets" "C:\Users\mikep\AppData\Roaming\Microsoft\UserSecrets\fa0fa59b-afe4-4960-9afc-18fcbc7fb41b" +mklink /J "L:\Git\AA\Compare\.vscode\.UserSecrets" "C:\Users\mikep\AppData\Roaming\Microsoft\UserSecrets\770b6ae3-266e-4d5f-970a-173709b064de" +``` diff --git a/.vscode/settings.json b/.vscode/settings.json index a38c24f..40c3c3a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -30,6 +30,7 @@ "paramref", "permille", "permyriad", + "pged", "Phares", "Phgtv", "Photoshop", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index bc2fa86..1888586 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,16 +13,6 @@ ], "problemMatcher": "$msCompile" }, - { - "label": "Format-Whitespaces", - "command": "dotnet", - "type": "process", - "args": [ - "format", - "whitespace" - ], - "problemMatcher": "$msCompile" - }, { "label": "User Secrets Set", "command": "dotnet", @@ -38,11 +28,34 @@ "problemMatcher": "$msCompile" }, { - "label": "Format", + "label": "Format-Rename-Whitespaces", "command": "dotnet", "type": "process", "args": [ "format", + "${workspaceFolder}/Rename/AA.Rename.csproj", + "whitespace" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "Format-Metadata-Whitespaces", + "command": "dotnet", + "type": "process", + "args": [ + "format", + "${workspaceFolder}/Metadata/AA.Metadata.csproj", + "whitespace" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "Format-Rename", + "command": "dotnet", + "type": "process", + "args": [ + "format", + "${workspaceFolder}/Rename/AA.Rename.csproj", "--report", ".vscode", "--verbosity", @@ -53,12 +66,112 @@ "problemMatcher": "$msCompile" }, { - "label": "build", + "label": "Format-Metadata", + "command": "dotnet", + "type": "process", + "args": [ + "format", + "${workspaceFolder}/Metadata/AA.Metadata.csproj", + "--report", + ".vscode", + "--verbosity", + "detailed", + "--severity", + "warn" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build-Shared", "command": "dotnet", "type": "process", "args": [ "build", - "${workspaceFolder}/AA.sln", + "${workspaceFolder}/Shared/AA.Shared.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build-Face", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Face/AA.Face.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build-People", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/People/AA.People.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build-Distance", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Distance/AA.Distance.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build-Metadata", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Metadata/AA.Metadata.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build-FaceRecognitionDotNet", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/FaceRecognitionDotNet/AA.FaceRecognitionDotNet.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build-Rename", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Rename/AA.Rename.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build-Compare", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Compare/AA.Compare.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], diff --git a/AA.sln b/AA.sln deleted file mode 100644 index 9799ffd..0000000 --- a/AA.sln +++ /dev/null @@ -1,34 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AA.Shared", "Shared\AA.Shared.csproj", "{74E6778C-FCBE-4959-AFED-7C528BC5F855}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AA.Metadata", "Metadata\AA.Metadata.csproj", "{9E1FE0DA-8A9C-49B4-82BC-67F68D42F49C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AA.Rename", "Rename\AA.Rename.csproj", "{E80BF7A6-C892-426D-829C-910BD4700E69}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {74E6778C-FCBE-4959-AFED-7C528BC5F855}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {74E6778C-FCBE-4959-AFED-7C528BC5F855}.Debug|Any CPU.Build.0 = Debug|Any CPU - {74E6778C-FCBE-4959-AFED-7C528BC5F855}.Release|Any CPU.ActiveCfg = Release|Any CPU - {74E6778C-FCBE-4959-AFED-7C528BC5F855}.Release|Any CPU.Build.0 = Release|Any CPU - {9E1FE0DA-8A9C-49B4-82BC-67F68D42F49C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9E1FE0DA-8A9C-49B4-82BC-67F68D42F49C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9E1FE0DA-8A9C-49B4-82BC-67F68D42F49C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9E1FE0DA-8A9C-49B4-82BC-67F68D42F49C}.Release|Any CPU.Build.0 = Release|Any CPU - {E80BF7A6-C892-426D-829C-910BD4700E69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E80BF7A6-C892-426D-829C-910BD4700E69}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E80BF7A6-C892-426D-829C-910BD4700E69}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E80BF7A6-C892-426D-829C-910BD4700E69}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/Compare/AA.Compare.csproj b/Compare/AA.Compare.csproj new file mode 100644 index 0000000..688f68c --- /dev/null +++ b/Compare/AA.Compare.csproj @@ -0,0 +1,50 @@ + + + enable + enable + Exe + win-x64 + net9.0 + 770b6ae3-266e-4d5f-970a-173709b064de + + + Phares.View.by.Distance.Compare + false + 9.0.100.0 + Mike Phares + Phares + true + snupkg + + + true + true + true + + + Windows + + + OSX + + + Linux + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Compare/Compare.cs b/Compare/Compare.cs new file mode 100644 index 0000000..5fb5703 --- /dev/null +++ b/Compare/Compare.cs @@ -0,0 +1,168 @@ +using Microsoft.Extensions.Logging; +using ShellProgressBar; +using System.Collections.ObjectModel; +using View_by_Distance.Compare.Models; +using View_by_Distance.Distance.Models.Stateless.Methods; +using View_by_Distance.Face.Models.Stateless.Methods; +using View_by_Distance.Metadata.Models; +using View_by_Distance.People.Models.Stateless.Methods; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.Compare; + +public partial class Compare : ICompare, IDisposable +{ + + private ProgressBar? _ProgressBar; + private readonly ProgressBarOptions _ProgressBarOptions; + + public Compare(List args, ILogger? logger, AppSettings appSettings, bool isSilent, IConsole console) + { + if (isSilent) + { } + if (args is null) + throw new NullReferenceException(nameof(args)); + if (console is null) + throw new NullReferenceException(nameof(console)); + ICompare compare = this; + long ticks = DateTime.Now.Ticks; + _ProgressBarOptions = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; + CompareWork(logger, appSettings, compare, ticks); + } + + void ICompare.Tick() => + _ProgressBar?.Tick(); + + void ICompare.ConstructProgressBar(int maxTicks, string message) + { + _ProgressBar?.Dispose(); + _ProgressBar = new(maxTicks, message, _ProgressBarOptions); + } + + void IDisposable.Dispose() + { + _ProgressBar?.Dispose(); + GC.SuppressFinalize(this); + } + + private static bool GetRunToDoCollectionFirst(AppSettings appSettings, long ticks) + { + bool result = appSettings.DistanceSettings.SaveSortingWithoutPerson; + if (!result) + result = !IId.IsOffsetDeterministicHashCode(appSettings.MetadataSettings); + if (!result) + { + string[] directories; + directories = Directory.GetDirectories(appSettings.ResultSettings.RootDirectory, "*", SearchOption.TopDirectoryOnly); + if (directories.Length == 0) + result = true; + else + { + string seasonDirectory; + DirectoryInfo directoryInfo; + DateTime dateTime = new(ticks); + string rootDirectory = appSettings.ResultSettings.RootDirectory; + (int season, string seasonName) = IDate.GetSeason(dateTime.DayOfYear); + string eDistanceContentDirectory = IResult.GetResultsDateGroupDirectory(appSettings.ResultSettings, nameof(E_Distance), appSettings.ResultSettings.ResultContent); + FileSystemInfo fileSystemInfo = new DirectoryInfo(eDistanceContentDirectory); + string[] checkDirectories = + [ + Path.Combine(rootDirectory, "Ancestry"), + Path.Combine(rootDirectory, "Facebook"), + Path.Combine(rootDirectory, "LinkedIn") + ]; + foreach (string checkDirectory in checkDirectories) + { + if (checkDirectory == rootDirectory) + 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) + { + directoryInfo = new(directory); + if (directoryInfo.LastWriteTime > fileSystemInfo.LastWriteTime) + { + result = true; + break; + } + } + } + } + } + if (result) + result = true; + if (!result) + result = false; + return result; + } + + private static ReadOnlyCollections GetReadOnlyCollections(AppSettings appSettings) + { + ReadOnlyCollections result; + ReadOnlyCollection personContainers = IPeople.GetPersonContainers(appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.PeopleSettings, appSettings.CompareSettings); + ReadOnlyCollection personKeys = IPeople.GetPersonKeys(personContainers); + result = IPeople.GetReadOnlyCollections(appSettings.ResultSettings, appSettings.PeopleSettings, appSettings.DistanceSettings, appSettings.CompareSettings, personContainers, personKeys); + return result; + } + + private static ReadOnlyCollection GetMappedExifDirectoryWithEncoding(AppSettings appSettings, ICompare compare, long ticks, ReadOnlyCollections readOnlyCollections) + { + ReadOnlyCollection results; + ReadOnlyCollection exifDirectories = IDistance.GetMapped(appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.PeopleSettings, appSettings.DistanceSettings, appSettings.CompareSettings, compare, ticks, readOnlyCollections); + if (exifDirectories.Count == 0 && !appSettings.DistanceSettings.SaveSortingWithoutPerson) + throw new NotSupportedException($"Switch {nameof(appSettings.DistanceSettings.SaveSortingWithoutPerson)}!"); + results = IDistance.GetMappedExifDirectoryWithEncoding(compare, ticks, exifDirectories); + if (results.Count == 0 && !appSettings.DistanceSettings.SaveSortingWithoutPerson) + throw new NotSupportedException($"Switch {nameof(appSettings.DistanceSettings.SaveSortingWithoutPerson)}!"); + return results; + } + + private void CompareWork(ILogger? logger, AppSettings appSettings, ICompare compare, long ticks) + { + const int updated = 0; + DistanceLimits? distanceLimits; + logger?.LogInformation("{Ticks}", ticks); + ReadOnlyCollection matrix; + ReadOnlyCollection saveContainers; + ReadOnlyCollection exifDirectories; + ReadOnlyCollection preFiltered; + ReadOnlyCollection postFiltered; + ReadOnlyDictionary onlyOne; + bool runToDoCollectionFirst = GetRunToDoCollectionFirst(appSettings, ticks); + ReadOnlyCollections readOnlyCollections = GetReadOnlyCollections(appSettings); + ReadOnlyCollection mappedExifDirectoryWithEncoding = GetMappedExifDirectoryWithEncoding(appSettings, compare, ticks, readOnlyCollections); + ReadOnlyDictionary> keyValuePairs = IDistance.Extract(appSettings.CompareSettings, mappedExifDirectoryWithEncoding); + foreach (string outputResolution in appSettings.CompareSettings.OutputResolutions) + { + if (runToDoCollectionFirst || outputResolution.Any(char.IsNumber)) + continue; + _ProgressBar?.Dispose(); + logger?.LogInformation("{outputResolution}", outputResolution); + exifDirectories = IFace.GetExifDirectories(appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.DistanceSettings, appSettings.CompareSettings, compare, ticks, outputResolution); + preFiltered = IDistance.GetPreFilterLocationContainer(appSettings.DistanceSettings, appSettings.CompareSettings, compare, ticks, readOnlyCollections, keyValuePairs, exifDirectories); + if (preFiltered.Count == 0) + continue; + distanceLimits = new(appSettings.DistanceSettings); + postFiltered = IDistance.GetPostFilterLocationContainer(preFiltered, distanceLimits); + if (postFiltered.Count == 0) + continue; + matrix = IDistance.GetMatrixLocationContainers(appSettings.DistanceSettings, appSettings.CompareSettings, compare, ticks, mappedExifDirectoryWithEncoding, distanceLimits, postFiltered); + if (matrix.Count == 0) + continue; + onlyOne = IDistance.GetOnlyOne(appSettings.DistanceSettings, matrix); + if (onlyOne.Count == 0) + continue; + saveContainers = IDistance.GetSaveContainers(appSettings.ResultSettings, appSettings.DistanceSettings, appSettings.CompareSettings, compare, ticks, outputResolution, onlyOne); + if (saveContainers.Count == 0) + continue; + IDistance.SaveContainers(appSettings.DistanceSettings, appSettings.CompareSettings, compare, ticks, updated, saveContainers); + } + } + +} \ No newline at end of file diff --git a/Compare/Models/AppSettings.cs b/Compare/Models/AppSettings.cs new file mode 100644 index 0000000..d6a8bcb --- /dev/null +++ b/Compare/Models/AppSettings.cs @@ -0,0 +1,78 @@ +using Microsoft.Extensions.Configuration; +using System.Text.Json; +using System.Text.Json.Serialization; +using View_by_Distance.Shared.Models; + +namespace View_by_Distance.Compare.Models; + +public record AppSettings(ResultSettings ResultSettings, + MetadataSettings MetadataSettings, + PeopleSettings PeopleSettings, + DistanceSettings DistanceSettings, + CompareSettings CompareSettings) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, AppSettingsSourceGenerationContext.Default.AppSettings); + return result; + } + + private static void Verify(AppSettings appSettings) + { + if (appSettings.DistanceSettings.RangeDaysDeltaTolerance.Length != 3) + throw new NullReferenceException(nameof(appSettings.DistanceSettings.RangeDaysDeltaTolerance)); + if (appSettings.DistanceSettings.RangeDistanceTolerance.Length != 3) + throw new NullReferenceException(nameof(appSettings.DistanceSettings.RangeDistanceTolerance)); + if (appSettings.DistanceSettings.RangeFaceAreaTolerance.Length != 3) + throw new NullReferenceException(nameof(appSettings.DistanceSettings.RangeFaceAreaTolerance)); + if (appSettings.DistanceSettings.RangeFaceConfidence.Length != 3) + throw new NullReferenceException(nameof(appSettings.DistanceSettings.RangeFaceConfidence)); + _ = DateTime.Now.AddDays(-appSettings.DistanceSettings.RangeDaysDeltaTolerance[1]); + if (appSettings.DistanceSettings.SaveSortingWithoutPerson && appSettings.PeopleSettings.JLinks.Length > 0) + throw new Exception("Settings has SaveSortingWithoutPerson and JLinks!"); + if (appSettings.DistanceSettings.SaveSortingWithoutPerson && !string.IsNullOrEmpty(appSettings.DistanceSettings.FocusModel)) + throw new Exception("Settings has SaveSortingWithoutPerson and FocusModel!"); + if (appSettings.DistanceSettings.SaveSortingWithoutPerson && !string.IsNullOrEmpty(appSettings.DistanceSettings.FocusDirectory)) + throw new Exception("Settings has SaveSortingWithoutPerson and FocusDirectory!"); + if (appSettings.CompareSettings.MaxDegreeOfParallelism > Environment.ProcessorCount) + throw new Exception("MaxDegreeOfParallelism must be =< Environment.ProcessorCount!"); + if (!string.IsNullOrEmpty(appSettings.DistanceSettings.FocusDirectory) && appSettings.DistanceSettings.FocusDirectory.Length != 2) + throw new NotSupportedException($"{nameof(appSettings.DistanceSettings.FocusDirectory)} currently only works with output directory! Example 00."); + } + + public static AppSettings Get(IConfigurationRoot configurationRoot) + { + AppSettings result; +#pragma warning disable IL3050, IL2026 + ResultSettings? resultSettings = configurationRoot.GetSection(nameof(ResultSettings)).Get(); + MetadataSettings? metadataSettings = configurationRoot.GetSection(nameof(MetadataSettings)).Get(); + PeopleSettings? peopleSettings = configurationRoot.GetSection(nameof(PeopleSettings)).Get(); + DistanceSettings? distanceSettings = configurationRoot.GetSection(nameof(DistanceSettings)).Get(); + CompareSettings? compareSettings = configurationRoot.GetSection(nameof(CompareSettings)).Get(); +#pragma warning restore IL3050, IL2026 + if (resultSettings is null || metadataSettings is null || peopleSettings is null || distanceSettings is null || compareSettings?.Company is null) + { + List paths = []; + foreach (IConfigurationProvider configurationProvider in configurationRoot.Providers) + { + if (configurationProvider is not Microsoft.Extensions.Configuration.Json.JsonConfigurationProvider jsonConfigurationProvider) + continue; + if (jsonConfigurationProvider.Source.FileProvider is not Microsoft.Extensions.FileProviders.PhysicalFileProvider physicalFileProvider) + continue; + paths.Add(physicalFileProvider.Root); + } + throw new NotSupportedException($"Not found!{Environment.NewLine}{string.Join(Environment.NewLine, paths.Distinct())}"); + } + result = new(resultSettings, metadataSettings, peopleSettings, distanceSettings, compareSettings); + Verify(result); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(AppSettings))] +internal partial class AppSettingsSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Compare/Models/CompareSettings.cs b/Compare/Models/CompareSettings.cs new file mode 100644 index 0000000..bc559be --- /dev/null +++ b/Compare/Models/CompareSettings.cs @@ -0,0 +1,30 @@ + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Compare.Models; + +public record CompareSettings(string Company, + string FacesFileNameExtension, + string FacesHiddenFileNameExtension, + string FacesPartsFileNameExtension, + string[] IgnoreExtensions, + int MaxDegreeOfParallelism, + string[] OutputResolutions, + string[] ValidImageFormatExtensions, + string[] ValidVideoFormatExtensions) : Shared.Models.Properties.ICompareSettings +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, CompareSettingsSourceGenerationContext.Default.CompareSettings); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(CompareSettings))] +internal partial class CompareSettingsSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Compare/Models/Identifier.cs b/Compare/Models/Identifier.cs new file mode 100644 index 0000000..32453b5 --- /dev/null +++ b/Compare/Models/Identifier.cs @@ -0,0 +1,32 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Compare.Models; + +internal sealed record Identifier(string[] DirectoryNames, + bool? HasDateTimeOriginal, + int Id, + long Length, + string PaddedId, + long Ticks) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, IdentifierSourceGenerationContext.Default.Identifier); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(Identifier))] +internal partial class IdentifierSourceGenerationContext : JsonSerializerContext +{ +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(Identifier[]))] +internal partial class IdentifierCollectionSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Compare/Program.cs b/Compare/Program.cs new file mode 100644 index 0000000..3aa6654 --- /dev/null +++ b/Compare/Program.cs @@ -0,0 +1,53 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using View_by_Distance.Compare.Models; + +namespace View_by_Distance.Compare; + +public class Program +{ + + public static void Secondary(ILogger logger, List args) + { + IConfigurationBuilder configurationBuilder = new ConfigurationBuilder() + .AddEnvironmentVariables() + .AddUserSecrets(); + IConfigurationRoot configurationRoot = configurationBuilder.Build(); + AppSettings appSettings = AppSettings.Get(configurationRoot); + int silentIndex = args.IndexOf("s"); + if (silentIndex > -1) + args.RemoveAt(silentIndex); + try + { + if (args is null) + throw new Exception("args is null!"); + Shared.Models.Console console = new(); + _ = new Compare(args, logger, appSettings, silentIndex > -1, console); + } + catch (Exception ex) + { + logger?.LogError(ex, "Error!"); + } + if (silentIndex > -1) + logger?.LogInformation("Done. Bye"); + else + { + logger?.LogInformation("Done. Press 'Enter' to end"); + _ = Console.ReadLine(); + } + } + + public static void Main(string[] args) + { +#pragma warning disable IL3050 + ILogger? logger = Host.CreateDefaultBuilder(args).Build().Services.GetRequiredService>(); +#pragma warning restore IL3050 + if (args is not null) + Secondary(logger, args.ToList()); + else + Secondary(logger, []); + } + +} \ No newline at end of file diff --git a/Distance/AA.Distance.csproj b/Distance/AA.Distance.csproj new file mode 100644 index 0000000..67ab276 --- /dev/null +++ b/Distance/AA.Distance.csproj @@ -0,0 +1,46 @@ + + + enable + enable + library + win-x64 + net9.0 + + + Phares.AA.Distance + false + 9.0.100.0 + Mike Phares + Phares + true + snupkg + + + true + true + true + + + Windows + + + OSX + + + Linux + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Distance/Models/C_Resize.cs b/Distance/Models/C_Resize.cs new file mode 100644 index 0000000..d2aaa10 --- /dev/null +++ b/Distance/Models/C_Resize.cs @@ -0,0 +1,3 @@ +namespace View_by_Distance.Metadata.Models; + +public class C_Resize() { } \ No newline at end of file diff --git a/Distance/Models/D2_FaceParts.cs b/Distance/Models/D2_FaceParts.cs new file mode 100644 index 0000000..77f7c75 --- /dev/null +++ b/Distance/Models/D2_FaceParts.cs @@ -0,0 +1,3 @@ +namespace View_by_Distance.Metadata.Models; + +public class D2_FaceParts() { } \ No newline at end of file diff --git a/Distance/Models/DistanceLimits.cs b/Distance/Models/DistanceLimits.cs new file mode 100644 index 0000000..8415138 --- /dev/null +++ b/Distance/Models/DistanceLimits.cs @@ -0,0 +1,59 @@ +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Methods; + +namespace View_by_Distance.Metadata.Models; + +public class DistanceLimits : IDistanceLimits +{ + + private int _Days; + private int _Distance; + + public double FaceAreaPermyriad { init; get; } + public double RangeDaysDeltaTolerance { init; get; } + public double FaceConfidencePercent { init; get; } + public double FaceDistancePermyriad { init; get; } + public int SortingMaximumPerFaceShouldBeHigh { init; get; } + public bool RangeDaysDeltaTargetLessThenUpper { init; get; } + public double RangeDistanceToleranceUpperLimit { init; get; } + + public DistanceLimits(DistanceSettings distanceSettings, int? useFiltersCounter = null) + { + RangeDistanceToleranceUpperLimit = distanceSettings.RangeDistanceTolerance[2]; + SortingMaximumPerFaceShouldBeHigh = distanceSettings.SortingMaximumPerFaceShouldBeHigh; + RangeDaysDeltaTargetLessThenUpper = distanceSettings.RangeDaysDeltaTolerance[1] > distanceSettings.RangeDaysDeltaTolerance[2]; + if (useFiltersCounter is null) + { + RangeDaysDeltaTolerance = distanceSettings.RangeDaysDeltaTolerance[1]; + FaceConfidencePercent = distanceSettings.FaceConfidencePercent * distanceSettings.RangeFaceConfidence[1]; + FaceAreaPermyriad = distanceSettings.FaceAreaPermyriad * distanceSettings.RangeFaceAreaTolerance[1]; + FaceDistancePermyriad = distanceSettings.FaceDistancePermyriad * distanceSettings.RangeDistanceTolerance[1]; + } + else + { + RangeDaysDeltaTolerance = ((distanceSettings.RangeDaysDeltaTolerance[2] - distanceSettings.RangeDaysDeltaTolerance[0]) * 0.01 * useFiltersCounter.Value) + distanceSettings.RangeDaysDeltaTolerance[1]; + FaceConfidencePercent = (distanceSettings.FaceConfidencePercent * ((distanceSettings.RangeFaceConfidence[2] - distanceSettings.RangeFaceConfidence[0]) * 0.01 * useFiltersCounter.Value)) + distanceSettings.RangeFaceConfidence[1]; + FaceDistancePermyriad = (distanceSettings.FaceDistancePermyriad * ((distanceSettings.RangeDistanceTolerance[2] - distanceSettings.RangeDistanceTolerance[0]) * 0.01 * useFiltersCounter.Value)) + distanceSettings.RangeDistanceTolerance[1]; + FaceAreaPermyriad = (distanceSettings.FaceAreaPermyriad * ((distanceSettings.RangeFaceAreaTolerance[2] - distanceSettings.RangeFaceAreaTolerance[0]) * 0.01 * useFiltersCounter.Value)) + distanceSettings.RangeFaceAreaTolerance[1]; + } + } + + string IDistanceLimits.GetCounts() + { + string result; + List<(int Value, string Name)> results = + [ + new(_Days, nameof(_Days)), + new(_Distance, nameof(_Distance)) + ]; + result = string.Join(' ', from l in results orderby l.Value descending select $"{l.Name}_{l.Value};"); + return result; + } + + void IDistanceLimits.AddCounts(int days, int distance) + { + _Days += days; + _Distance += distance; + } + +} \ No newline at end of file diff --git a/Distance/Models/E_Distance.cs b/Distance/Models/E_Distance.cs new file mode 100644 index 0000000..cadc3ce --- /dev/null +++ b/Distance/Models/E_Distance.cs @@ -0,0 +1,3 @@ +namespace View_by_Distance.Metadata.Models; + +public class E_Distance() { } \ No newline at end of file diff --git a/Distance/Models/IDistanceLimits.cs b/Distance/Models/IDistanceLimits.cs new file mode 100644 index 0000000..8990fa3 --- /dev/null +++ b/Distance/Models/IDistanceLimits.cs @@ -0,0 +1,17 @@ +namespace View_by_Distance.Shared.Models.Methods; + +public interface IDistanceLimits +{ + + public double FaceAreaPermyriad { init; get; } + public double RangeDaysDeltaTolerance { init; get; } + public double FaceConfidencePercent { init; get; } + public double FaceDistancePermyriad { init; get; } + public int SortingMaximumPerFaceShouldBeHigh { init; get; } + public bool RangeDaysDeltaTargetLessThenUpper { init; get; } + public double RangeDistanceToleranceUpperLimit { init; get; } + + string GetCounts(); + void AddCounts(int days, int distance); + +} \ No newline at end of file diff --git a/Distance/Models/Stateless/FaceEncodingLogic.cs b/Distance/Models/Stateless/FaceEncodingLogic.cs new file mode 100644 index 0000000..13d0912 --- /dev/null +++ b/Distance/Models/Stateless/FaceEncodingLogic.cs @@ -0,0 +1,35 @@ +using System.Collections.ObjectModel; +using System.Text.Json; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.Distance.Models.Stateless.Methods; + +internal static class FaceEncodingLogic +{ + + internal static ReadOnlyCollection GetMappedExifDirectoryWithEncoding(ICompare compare, long ticks, ReadOnlyCollection exifDirectories) + { + List results = []; + string? json; + FaceEncoding? faceEncoding; + ExifDirectory exifDirectory; + FaceRecognitionDotNet.FaceEncoding? encoding; + int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); + string message = $") Building Mapped with Encoding Face Files Collection - {totalSeconds} total second(s)"; + compare.ConstructProgressBar(exifDirectories.Count, message); + foreach (ExifDirectory e in exifDirectories) + { + compare.Tick(); + json = Metadata.Models.Stateless.Methods.IMetadata.GetFaceEncoding(e); + faceEncoding = json is null ? null : JsonSerializer.Deserialize(json); + if (faceEncoding is null) + continue; + encoding = FaceRecognitionDotNet.FaceRecognition.LoadFaceEncoding(faceEncoding.RawEncoding); + exifDirectory = ExifDirectory.Get(encoding, e); + results.Add(exifDirectory); + } + return results.AsReadOnly(); + } + +} \ No newline at end of file diff --git a/Distance/Models/Stateless/FilterLogicA.cs b/Distance/Models/Stateless/FilterLogicA.cs new file mode 100644 index 0000000..383aba9 --- /dev/null +++ b/Distance/Models/Stateless/FilterLogicA.cs @@ -0,0 +1,93 @@ +using System.Collections.ObjectModel; +using System.Text.Json; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.Distance.Models.Stateless.Methods; + +internal static class FilterLogicA +{ + + private static bool? CanReMap(LocationContainer locationContainer) + { + bool? result = null; + ArgumentNullException.ThrowIfNull(locationContainer); + // if (locationContainer.WholePercentages is null) + // result = null; + // else + // throw new NotImplementedException(); + return result; + } + + private static bool? IsFocusPerson(DistanceSettings distanceSettings, ReadOnlyCollections readOnlyCollections, LocationContainer locationContainer) + { + bool? result; + if (distanceSettings.SkipPersonWithMoreThen < 1 && readOnlyCollections.JLinkResolvedPersonKeys.Count == 0) + result = null; + else if (locationContainer.WholePercentages is null) + result = null; + else + throw new NotImplementedException(); + return result; + } + + internal static ReadOnlyCollection GetPreFilterLocationContainer(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollections readOnlyCollections, ReadOnlyDictionary> keyValuePairs, ReadOnlyCollection exifDirectories) + { + List results = []; + string? json; + string? model; + bool? canReMap; + bool? isFocusPerson; + bool? inSkipCollection; + FaceEncoding? faceEncoding; + FaceRecognitionDotNet.FaceEncoding? encoding; + ReadOnlyDictionary? keyValues; + List? wholePercentagesCollection; + ReadOnlyCollection locationContainers = FilterLogicB.GetLocationContainers(distanceSettings, compareSettings, compare, ticks, exifDirectories, nameof(FilterLogicA)); + int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); + string message = $") PreFiltering LocationContainers Face Files Collection - {totalSeconds} total second(s)"; + compare.ConstructProgressBar(locationContainers.Count, message); + foreach (LocationContainer locationContainer in locationContainers) + { + compare.Tick(); + if (locationContainer.FilePath.Id is null || locationContainer.WholePercentages is null) + continue; + if (keyValuePairs.TryGetValue(locationContainer.FilePath.Id.Value, out keyValues)) + { + if (keyValues.ContainsKey(locationContainer.WholePercentages.Value)) + continue; + } + if (locationContainer.ExifDirectory is null || locationContainer.FaceFile is null) + continue; + inSkipCollection = readOnlyCollections.SkipNotSkipCollection.TryGetValue(locationContainer.FilePath.Id.Value, out wholePercentagesCollection) && wholePercentagesCollection.Any(l => l.WholePercentages == locationContainer.WholePercentages.Value); + if (inSkipCollection is not null && inSkipCollection.Value) + continue; + canReMap = CanReMap(locationContainer); + if (canReMap is not null && !canReMap.Value) + continue; + isFocusPerson = IsFocusPerson(distanceSettings, readOnlyCollections, locationContainer); + if (isFocusPerson is not null && !isFocusPerson.Value) + continue; + if (!string.IsNullOrEmpty(distanceSettings.FocusModel)) + { + model = Metadata.Models.Stateless.Methods.IMetadata.GetModel(locationContainer.ExifDirectory); + if (string.IsNullOrEmpty(model) || !model.Contains(distanceSettings.FocusModel)) + continue; + } + if (!string.IsNullOrEmpty(distanceSettings.FocusDirectory)) + { + if (!locationContainer.FilePath.DirectoryFullPath.Contains(distanceSettings.FocusDirectory)) + continue; + } + json = Metadata.Models.Stateless.Methods.IMetadata.GetFaceEncoding(locationContainer.ExifDirectory); + faceEncoding = json is null ? null : JsonSerializer.Deserialize(json); + if (faceEncoding is null) + continue; + encoding = FaceRecognitionDotNet.FaceRecognition.LoadFaceEncoding(faceEncoding.RawEncoding); + results.Add(LocationContainer.Get(locationContainer, encoding, keepExifDirectory: false)); + } + return results.AsReadOnly(); + } + +} \ No newline at end of file diff --git a/Distance/Models/Stateless/FilterLogicB.cs b/Distance/Models/Stateless/FilterLogicB.cs new file mode 100644 index 0000000..6042e46 --- /dev/null +++ b/Distance/Models/Stateless/FilterLogicB.cs @@ -0,0 +1,80 @@ +using System.Collections.ObjectModel; +using System.Drawing; +using System.Text.Json; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.Distance.Models.Stateless.Methods; + +internal static class FilterLogicB +{ + + private static void MoveUnableToMatch(FilePath filePath) + { + string checkFile = $"{filePath.FullName}.unk"; + if (File.Exists(filePath.FullName) && !File.Exists(checkFile)) + File.Move(filePath.FullName, checkFile); + } + + private static void LocationContainersParallelFor(DistanceSettings distanceSettings, ICompareSettings compareSettings, List locationContainers, ExifDirectory exifDirectory) + { + string? json; + if (exifDirectory.FilePath.Id is null) + return; + DateOnly dateOnly = DateOnly.FromDateTime(new DateTime(exifDirectory.FilePath.CreationTicks)); + int? wholePercentages = IMapping.GetWholePercentages(compareSettings, exifDirectory.FilePath); + if (wholePercentages is null) + { + if (distanceSettings.DistanceMoveUnableToMatch) + MoveUnableToMatch(exifDirectory.FilePath); + return; + } + json = Metadata.Models.Stateless.Methods.IMetadata.GetOutputResolution(exifDirectory); + if (json is null || !json.Contains(nameof(DateTime))) + { + if (distanceSettings.DistanceMoveUnableToMatch) + MoveUnableToMatch(exifDirectory.FilePath); + return; + } + FaceFile? faceFile = JsonSerializer.Deserialize(json, FaceFileGenerationContext.Default.FaceFile); + if (faceFile is null || faceFile.Location is null) + { + if (distanceSettings.DistanceMoveUnableToMatch) + MoveUnableToMatch(exifDirectory.FilePath); + return; + } + RectangleF? rectangle = Shared.Models.Stateless.Methods.ILocation.GetPercentagesRectangle(distanceSettings, wholePercentages.Value); + if (rectangle is null) + return; + LocationContainer locationContainer = new(dateOnly, + exifDirectory, + exifDirectory.Encoding, + faceFile, + exifDirectory.FilePath, + null, + null, + exifDirectory.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, + rectangle, + wholePercentages); + lock (locationContainers) + locationContainers.Add(locationContainer); + } + + internal static ReadOnlyCollection GetLocationContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollection exifDirectories, string sourceClass) + { + List results = []; + int maxDegreeOfParallelism = compareSettings.MaxDegreeOfParallelism; + int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); + ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; + string message = $") Building LocationContainers Face Files Collection {sourceClass} - {totalSeconds} total second(s)"; + compare.ConstructProgressBar(exifDirectories.Count, message); + _ = Parallel.For(0, exifDirectories.Count, parallelOptions, (i, state) => + { + compare.Tick(); + LocationContainersParallelFor(distanceSettings, compareSettings, results, exifDirectories[i]); + }); + return results.AsReadOnly(); + } + +} \ No newline at end of file diff --git a/Distance/Models/Stateless/FilterLogicC.cs b/Distance/Models/Stateless/FilterLogicC.cs new file mode 100644 index 0000000..dde92d0 --- /dev/null +++ b/Distance/Models/Stateless/FilterLogicC.cs @@ -0,0 +1,100 @@ +using System.Collections.ObjectModel; +using View_by_Distance.Metadata.Models; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.Distance.Models.Stateless.Methods; + +internal static class FilterLogicC +{ + + internal static ReadOnlyCollection GetPostFilterLocationContainer(ReadOnlyCollection preFiltered, DistanceLimits distanceLimits) + { + List results = []; + foreach (LocationContainer locationContainer in preFiltered) + { + if (locationContainer.FaceFile is null) + continue; + if (locationContainer.FaceFile.AreaPermyriad < distanceLimits.FaceAreaPermyriad) + continue; + if (locationContainer.FaceFile.ConfidencePercent < distanceLimits.FaceConfidencePercent) + continue; + results.Add(locationContainer); + } + return results.AsReadOnly(); + } + + private static ReadOnlyCollection GetCombined(DistanceSettings distanceSettings, Shared.Models.Properties.ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollection exifDirectories, ReadOnlyCollection postFiltered) + { + List results = []; + foreach (LocationContainer locationContainer in postFiltered) + results.Add(locationContainer); + ReadOnlyCollection locationContainers = FilterLogicB.GetLocationContainers(distanceSettings, compareSettings, compare, ticks, exifDirectories, nameof(FilterLogicC)); + foreach (LocationContainer locationContainer in locationContainers) + results.Add(locationContainer); + return results.AsReadOnly(); + } + + internal static ReadOnlyCollection GetMatrixLocationContainers(DistanceSettings distanceSettings, Shared.Models.Properties.ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollection mappedExifDirectoryWithEncoding, DistanceLimits distanceLimits, ReadOnlyCollection postFiltered) + { + List results = []; + ReadOnlyCollection collection; + ReadOnlyCollection locationContainers = GetCombined(distanceSettings, compareSettings, compare, ticks, mappedExifDirectoryWithEncoding, postFiltered); + string message = $") Building Matrix - {(int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds)} total second(s)"; + compare.ConstructProgressBar(postFiltered.Count, message); + foreach (LocationContainer locationContainer in postFiltered) + { + compare.Tick(); + if (locationContainer.FilePath.Id is null) + continue; + collection = FaceRecognitionDotNet.FaceRecognition.GetLocationContainers(distanceSettings.FaceDistancePermyriad, locationContainers, locationContainer); + foreach (LocationContainer l in collection) + { + // if (locationContainer.FilePath.Id == -626078035 && l.FilePath.Id == -626078035) + // continue; + if (l.FilePath.Id is null) + continue; + if (l.LengthPermyriad is null) + continue; + if (l.LengthPermyriad > distanceLimits.FaceDistancePermyriad) + break; + if (!distanceSettings.SaveSortingWithoutPerson && l.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName is null) + continue; + if (l.FilePath.Id.Value == locationContainer.FilePath.Id.Value && l.WholePercentages == locationContainer.WholePercentages) + continue; + results.Add(l); + } + } + LocationContainer[] array = results.OrderBy(l => l.LengthPermyriad).ToArray(); + return array.AsReadOnly(); + } + + internal static ReadOnlyDictionary GetOnlyOne(DistanceSettings distanceSettings, ReadOnlyCollection matrix) + { + Dictionary results = []; + List added = []; + LocationContainer? tryGetValue; + foreach (LocationContainer locationContainer in matrix) + { + if (distanceSettings.SaveIndividually) + break; + if (locationContainer.LengthSource is null) + continue; + if (distanceSettings.UseExtraPersonKeyCheck) + { + if (results.TryGetValue(locationContainer.LengthSource.Name, out tryGetValue)) + { + if (locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName is not null && tryGetValue.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName is not null && locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName.KeyTicks != tryGetValue.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName.KeyTicks) + _ = results.Remove(locationContainer.LengthSource.Name); + continue; + } + } + if (added.Contains(locationContainer.LengthSource.Name)) + continue; + added.Add(locationContainer.LengthSource.Name); + results.Add(locationContainer.LengthSource.Name, locationContainer); + } + return results.AsReadOnly(); + } + +} \ No newline at end of file diff --git a/Distance/Models/Stateless/FilterLogicD.cs b/Distance/Models/Stateless/FilterLogicD.cs new file mode 100644 index 0000000..575a634 --- /dev/null +++ b/Distance/Models/Stateless/FilterLogicD.cs @@ -0,0 +1,288 @@ +using Humanizer; +using System.Collections.ObjectModel; +using View_by_Distance.Metadata.Models; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; +using View_by_Distance.Shared.Models.Stateless.Methods; +using WindowsShortcutFactory; + +namespace View_by_Distance.Distance.Models.Stateless.Methods; + +internal static class FilterLogicD +{ + + internal record RecordA(long? Ticks, string? Directory); + + internal record RecordB(string ByValue, bool IsByMapping, bool IsBySorting); + + internal record RecordC(string? DebugDirectory, string? Directory, long? Ticks, string? PersonDirectory); + + internal static RecordB Get(int? useFiltersCounter, bool saveIndividually, bool sortingContainersAny, string forceSingleImageHumanized, int? distancePermyriad, int? by, string? displayDirectoryName) + { + RecordB result; + string byValue; + bool isByMapping; + bool isBySorting; + if (by is null) + { + isByMapping = false; + isBySorting = !sortingContainersAny; + byValue = $"{nameof(Shared.Models.Stateless.IMapLogic.Mapping)}Null"; + } + else + { + isByMapping = by == Shared.Models.Stateless.IMapLogic.Mapping; + isBySorting = by == Shared.Models.Stateless.IMapLogic.Sorting; + bool isDefaultName = displayDirectoryName is not null && IPerson.IsDefaultName(displayDirectoryName); + if (isBySorting && displayDirectoryName is null) + byValue = saveIndividually ? nameof(Shared.Models.Stateless.IMapLogic.Individually) : $"{nameof(Shared.Models.Stateless.IMapLogic.Sorting)} Without Person{(distancePermyriad < 2000 ? "-A" : "-Z")}"; + else if (isBySorting && useFiltersCounter.HasValue) + byValue = $"{nameof(Shared.Models.Stateless.IMapLogic.Sorting)}{(!isDefaultName ? "-A" : "-Z")} Modified Filters - {useFiltersCounter.Value}"; + else + { + byValue = $"{by.Value switch + { + Shared.Models.Stateless.IMapLogic.Mapping => nameof(Shared.Models.Stateless.IMapLogic.Mapping), + Shared.Models.Stateless.IMapLogic.Sorting => saveIndividually ? nameof(Shared.Models.Stateless.IMapLogic.Individually) : nameof(Shared.Models.Stateless.IMapLogic.Sorting), + Shared.Models.Stateless.IMapLogic.ForceSingleImage => forceSingleImageHumanized, + _ => throw new NotImplementedException() + }}{(!isDefaultName ? "-A" : "-Z")}"; + } + } + result = new(byValue, isByMapping, isBySorting); + return result; + } + + private static RecordB Get(int? useFiltersCounter, bool saveIndividually, bool sortingContainersAny, string forceSingleImageHumanized, int? distancePermyriad, PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName) => + Get(useFiltersCounter, saveIndividually, sortingContainersAny, forceSingleImageHumanized, distancePermyriad, personKeyFormattedAndKeyTicksAndDisplayDirectoryName is null ? null : Shared.Models.Stateless.IMapLogic.Mapping, personKeyFormattedAndKeyTicksAndDisplayDirectoryName?.DisplayDirectoryName); + + private static RecordC Get(string eDistanceContentTicksDirectory, RecordB recordB, PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName, string segmentB) + { + RecordC result; + if (string.IsNullOrEmpty(personKeyFormattedAndKeyTicksAndDisplayDirectoryName?.DisplayDirectoryName)) + throw new NotImplementedException(); + long? ticks = null; + string? debugDirectory = Path.Combine(eDistanceContentTicksDirectory, recordB.ByValue, personKeyFormattedAndKeyTicksAndDisplayDirectoryName.KeyFormatted, personKeyFormattedAndKeyTicksAndDisplayDirectoryName.DisplayDirectoryName); + string? directory = Path.Combine(eDistanceContentTicksDirectory, recordB.ByValue, personKeyFormattedAndKeyTicksAndDisplayDirectoryName.KeyFormatted, segmentB); + string? personDirectory = Path.Combine(directory, personKeyFormattedAndKeyTicksAndDisplayDirectoryName.DisplayDirectoryName, "lnk"); + result = new(debugDirectory, directory, ticks, personDirectory); + return result; + } + + internal static string GetResizeContentDirectory(ResultSettings resultSettings, string cContentDirectory, FilePath filePath) + { + string result; + (string directoryName, _) = IPath.GetDirectoryNameAndIndex(resultSettings, filePath); + result = Path.Combine(cContentDirectory, directoryName); + return result; + } + + internal static string GetFacePartsDirectoryX(ResultSettings resultSettings, string d2FacePartsContentDirectory, FilePath filePath) + { + string result; + (string directoryName, _) = IPath.GetDirectoryNameAndIndex(resultSettings, filePath); + result = Path.Combine(d2FacePartsContentDirectory, directoryName, filePath.NameWithoutExtension); + return result; + } + + internal static ReadOnlyCollection GetSaveContainers(ResultSettings resultSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, string outputResolution, ReadOnlyDictionary onlyOne) + { + List results = []; + RecordB recordB; + RecordC recordC; + string segmentB; + string checkFile; + string? directory; + string shortcutFile; + string facesDirectory; + bool isCounterPersonYear; + string facePartsDirectory; + FileHolder? faceFileHolder; + SaveContainer? saveContainer; + FileHolder? resizedFileHolder; + int? useFiltersCounter = null; + string resizeContentDirectory; + FileHolder? facePartsFileHolder; + FileHolder? hiddenFaceFileHolder; + LocationContainer locationContainer; + bool sortingContainersAny = onlyOne.Count > 0; + string eResultsFullGroupDirectory = IResult.GetResultsDateGroupDirectory(resultSettings, + nameof(E_Distance), + resultSettings.ResultContent); + string cResultsFullGroupDirectory = IResult.GetResultsFullGroupDirectory(resultSettings, + nameof(C_Resize), + outputResolution, + includeResizeGroup: true, + includeModel: false, + includePredictorModel: false); + string d2ResultsFullGroupDirectory = IResult.GetResultsFullGroupDirectory(resultSettings, + nameof(D2_FaceParts), + outputResolution, + includeResizeGroup: true, + includeModel: true, + includePredictorModel: true); + string cContentDirectory = Path.Combine(cResultsFullGroupDirectory, resultSettings.ResultContent); + if (!Directory.Exists(cContentDirectory)) + _ = Directory.CreateDirectory(cContentDirectory); + string eDistanceContentTicksDirectory = Path.Combine(eResultsFullGroupDirectory, ticks.ToString()); + if (!Directory.Exists(eDistanceContentTicksDirectory)) + _ = Directory.CreateDirectory(eDistanceContentTicksDirectory); + string d2FacePartsContentDirectory = Path.Combine(d2ResultsFullGroupDirectory, resultSettings.ResultContent); + if (!Directory.Exists(d2FacePartsContentDirectory)) + _ = Directory.CreateDirectory(d2FacePartsContentDirectory); + string forceSingleImageHumanized = nameof(Shared.Models.Stateless.IMapLogic.ForceSingleImage).Humanize(LetterCasing.Title); + string message = $") Building Save Container Collection - {(int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds)} total second(s)"; + compare.ConstructProgressBar(onlyOne.Count, message); + foreach (KeyValuePair keyValuePair in onlyOne) + { + if (distanceSettings.SaveIndividually) + break; + locationContainer = keyValuePair.Value; + if (locationContainer.LengthPermyriad is null || locationContainer.LengthSource is null) + continue; + segmentB = locationContainer.LengthPermyriad.Value.ToString().PadLeft(2, '0')[..2]; + if (locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName is null) + continue; + isCounterPersonYear = IPersonBirthday.IsCounterPersonYear(locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName.KeyTicks); + recordB = Get(useFiltersCounter, distanceSettings.SaveIndividually, sortingContainersAny, forceSingleImageHumanized, locationContainer.LengthPermyriad, locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName); + recordC = Get(eDistanceContentTicksDirectory, recordB, locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, segmentB); + if (string.IsNullOrEmpty(recordC.Directory) || string.IsNullOrEmpty(recordC.PersonDirectory)) + continue; + directory = recordC.Directory; + if (!string.IsNullOrEmpty(recordC.DebugDirectory)) + results.Add(SaveContainer.Get(recordC.DebugDirectory)); + if (locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName is null) + { + if (!distanceSettings.SaveSortingWithoutPerson) + throw new NotSupportedException(); + if (recordC.Ticks is null) + continue; + } + results.Add(SaveContainer.Get(recordC.PersonDirectory)); + facesDirectory = locationContainer.LengthSource.DirectoryFullPath; + faceFileHolder = FileHolder.Get(locationContainer.LengthSource.FullName); + checkFile = Path.Combine(directory, $"{locationContainer.LengthSource.Name}"); + shortcutFile = Path.Combine(recordC.PersonDirectory, $"{locationContainer.LengthSource.Name}.lnk"); + resizeContentDirectory = GetResizeContentDirectory(resultSettings, cContentDirectory, locationContainer.LengthSource); + facePartsDirectory = GetFacePartsDirectoryX(resultSettings, d2FacePartsContentDirectory, locationContainer.LengthSource); + hiddenFaceFileHolder = FileHolder.Get(Path.Combine(facesDirectory, $"{locationContainer.LengthSource.NameWithoutExtension}{compareSettings.FacesHiddenFileNameExtension}")); + facePartsFileHolder = FileHolder.Get(Path.Combine(facePartsDirectory, $"{locationContainer.LengthSource.NameWithoutExtension}{compareSettings.FacesPartsFileNameExtension}")); + resizedFileHolder = FileHolder.Get(Path.Combine(resizeContentDirectory, $"{locationContainer.LengthSource.FileNameFirstSegment}{Path.GetExtension(locationContainer.LengthSource.NameWithoutExtension)}")); + saveContainer = SaveContainer.Get(checkFile, directory, faceFileHolder, hiddenFaceFileHolder, facePartsFileHolder, resizedFileHolder, shortcutFile); + results.Add(saveContainer); + } + return results.AsReadOnly(); + } + + internal static void SaveContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, int? updated, ReadOnlyCollection saveContainers) + { + string fileName; + string checkFile; + string sourceFile; + List distinct = []; + WindowsShortcut windowsShortcut; + string[] directories = (from l in saveContainers select l.Directory).Distinct().ToArray(); + foreach (string directory in directories) + { + if (string.IsNullOrEmpty(directory)) + continue; + if (!Directory.Exists(directory)) + _ = Directory.CreateDirectory(directory); + } + int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); + string message; + if (updated is null) + message = $") {saveContainers.Count:000} save(s) - {totalSeconds} total second(s)"; + else + message = $") {saveContainers.Count:000} save(s) - {updated} Updated - {totalSeconds} total second(s)"; + compare.ConstructProgressBar(saveContainers.Count, message); + foreach (SaveContainer saveContainer in saveContainers) + { + compare.Tick(); + if (string.IsNullOrEmpty(saveContainer.Directory) || string.IsNullOrEmpty(saveContainer.CheckFile) || saveContainer.FaceFileHolder is null) + continue; + if (saveContainer.FacePartsFileHolder is null && saveContainer.HiddenFaceFileHolder is null && saveContainer.ResizedFileHolder is null) + { + checkFile = saveContainer.CheckFile; + sourceFile = saveContainer.FaceFileHolder.FullName; + } + else if (!saveContainer.FaceFileHolder.Exists && saveContainer.ResizedFileHolder is not null && saveContainer.ResizedFileHolder.Exists) + { + checkFile = saveContainer.CheckFile; + sourceFile = saveContainer.ResizedFileHolder.FullName; + } + else if (saveContainer.FaceFileHolder.Exists) + { + sourceFile = saveContainer.FaceFileHolder.FullName; + checkFile = $"{saveContainer.CheckFile}{compareSettings.FacesFileNameExtension}"; + } + else + continue; + if (distanceSettings.SaveIndividually) + { + fileName = Path.GetFileName(checkFile); + if (distinct.Contains(fileName)) + continue; + distinct.Add(fileName); + } + if (File.Exists(checkFile)) + continue; + File.Copy(sourceFile, checkFile); + if (distanceSettings.SaveIndividually) + continue; + if (saveContainer.MakeAllHidden) + File.SetAttributes(checkFile, FileAttributes.Hidden); + if (saveContainer.HiddenFaceFileHolder is not null && saveContainer.HiddenFaceFileHolder.Exists) + { + sourceFile = saveContainer.HiddenFaceFileHolder.FullName; + checkFile = $"{saveContainer.CheckFile}{compareSettings.FacesHiddenFileNameExtension}"; + } + else if (saveContainer.FacePartsFileHolder is not null && saveContainer.FacePartsFileHolder.Exists) + { + sourceFile = saveContainer.FacePartsFileHolder.FullName; + checkFile = $"{saveContainer.CheckFile}{compareSettings.FacesPartsFileNameExtension}"; + } + if (File.Exists(checkFile)) + continue; + File.Copy(sourceFile, checkFile); + if (saveContainer.MakeAllHidden) + File.SetAttributes(checkFile, FileAttributes.Hidden); + } + if (updated is null) + { + foreach (SaveContainer saveContainer in saveContainers) + { + if (string.IsNullOrEmpty(saveContainer.Directory) || string.IsNullOrEmpty(saveContainer.CheckFile) || saveContainer.ResizedFileHolder is null || !saveContainer.ResizedFileHolder.Exists) + continue; + checkFile = saveContainer.CheckFile; + sourceFile = saveContainer.ResizedFileHolder.FullName; + if (File.Exists(checkFile)) + continue; + File.Copy(sourceFile, checkFile); + if (saveContainer.MakeAllHidden) + File.SetAttributes(checkFile, FileAttributes.Hidden); + } + } + foreach (SaveContainer saveContainer in saveContainers) + { + if (string.IsNullOrEmpty(saveContainer.Directory) || string.IsNullOrEmpty(saveContainer.ShortcutFile) || saveContainer.ResizedFileHolder is null || !saveContainer.ResizedFileHolder.Exists) + continue; + try + { + string description; + if (saveContainer.FaceFileHolder is not null && saveContainer.FaceFileHolder.Name.StartsWith(saveContainer.ResizedFileHolder.Name)) + description = saveContainer.FaceFileHolder.Name; + else + description = saveContainer.ResizedFileHolder.Name; + windowsShortcut = new() { Path = saveContainer.ResizedFileHolder.FullName, Description = description }; + windowsShortcut.Save(saveContainer.ShortcutFile); + windowsShortcut.Dispose(); + if (saveContainer.MakeAllHidden) + File.SetAttributes(saveContainer.ShortcutFile, FileAttributes.Hidden); + } + catch (Exception) + { } + } + } + +} \ No newline at end of file diff --git a/Distance/Models/Stateless/MappedLogicA.cs b/Distance/Models/Stateless/MappedLogicA.cs new file mode 100644 index 0000000..b66acad --- /dev/null +++ b/Distance/Models/Stateless/MappedLogicA.cs @@ -0,0 +1,193 @@ +using System.Collections.ObjectModel; +using View_by_Distance.Metadata.Models; +using View_by_Distance.Metadata.Models.Stateless.Methods; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.Distance.Models.Stateless.Methods; + +internal static class MappedLogicA +{ + + internal record MappedFile(PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, + string? PersonDisplayDirectoryName, + FilePath FilePath); + + private static List GetDisplayDirectoryAllFiles(PeopleSettings peopleSettings, ICompareSettings compareSettings, ReadOnlyCollections readOnlyCollections) + { + List results = []; + MappedFile mappedFile; + string personKeyFormatted; + List distinct = []; + PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName personKeyFormattedAndKeyTicksAndDisplayDirectoryName; + foreach (PersonContainer personContainer in readOnlyCollections.PersonContainers) + { + if (personContainer.Key is null) + continue; + for (int i = personContainer.DisplayDirectoryAllFilePaths.Count - 1; i > -1; i--) + { + if (personContainer.DisplayDirectoryAllFilePaths[i].ExtensionLowered != compareSettings.FacesFileNameExtension) + continue; + if (distinct.Contains(personContainer.DisplayDirectoryAllFilePaths[i].Name)) + continue; + distinct.Add(personContainer.DisplayDirectoryAllFilePaths[i].Name); + personKeyFormatted = IPersonBirthday.GetFormatted(peopleSettings.PersonBirthdayFormat, personContainer.Key.Value); + personKeyFormattedAndKeyTicksAndDisplayDirectoryName = new(personKeyFormatted, personContainer.Key.Value, personContainer.DisplayDirectoryAllFilePaths[i].Name); + mappedFile = new(personKeyFormattedAndKeyTicksAndDisplayDirectoryName, personContainer.DisplayDirectoryName, personContainer.DisplayDirectoryAllFilePaths[i]); + results.Add(mappedFile); + } + } + return results; + } + + private static ReadOnlyCollection GetMappedFiles(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, ICompareSettings compareSettings, ReadOnlyCollections readOnlyCollections, ReadOnlyCollection records) + { + List results = []; + long personKey; + string checkFile; + FilePath filePath; + FileHolder fileHolder; + MappedFile mappedFile; + List distinct = []; + PersonBirthday? personBirthday; + PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName personKeyFormattedAndKeyTicksAndDisplayDirectoryName; + results.AddRange(GetDisplayDirectoryAllFiles(peopleSettings, compareSettings, readOnlyCollections)); + foreach (MappedLogicB.Record record in records) + { + personBirthday = IPersonBirthday.GetPersonBirthday(peopleSettings.PersonBirthdayFormat, record.PersonKeyFormatted); + if (personBirthday is null) + continue; + if (distinct.Contains(record.MappedFaceFilePath.Name)) + continue; + distinct.Add(record.MappedFaceFilePath.Name); + personKey = personBirthday.Value.Ticks; + personKeyFormattedAndKeyTicksAndDisplayDirectoryName = new(record.PersonKeyFormatted, personKey, record.PersonDisplayDirectoryName); + mappedFile = new(personKeyFormattedAndKeyTicksAndDisplayDirectoryName, record.PersonDisplayDirectoryName, record.MappedFaceFilePath); + results.Add(mappedFile); + } + for (int i = results.Count - 1; i > -1; i--) + { + filePath = results[i].FilePath; + if (filePath.Name.EndsWith(".old")) + { + results.RemoveAt(i); + continue; + } + if (!filePath.Name.EndsWith(".abd") && !filePath.Name.EndsWith(".brt") && !filePath.Name.EndsWith(".dup") && !filePath.Name.EndsWith(".unk")) + continue; + checkFile = filePath.FullName[..^4]; + if (File.Exists(checkFile)) + continue; + File.Move(filePath.FullName, checkFile); + fileHolder = FileHolder.Get(checkFile); + filePath = FilePath.Get(resultSettings, metadataSettings, fileHolder, index: null); + results[i] = new(results[i].PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, results[i].PersonDisplayDirectoryName, filePath); + } + for (int i = results.Count - 1; i > -1; i--) + { + if (File.Exists(results[i].FilePath.FullName)) + continue; + results.RemoveAt(i); + } + return results.AsReadOnly(); + } + + private static void MappedParallelFor(ResultSettings resultSettings, MetadataSettings metadataSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, List exifDirectories, ReadOnlyDictionary> skipCollection, MappedFile mappedFile) + { + int? id; + int? wholePercentages; + const string lnk = ".lnk"; + if (!mappedFile.FilePath.Name.EndsWith(lnk)) + { + if (mappedFile.FilePath.Id is null) + return; + id = mappedFile.FilePath.Id; + wholePercentages = IMapping.GetWholePercentages(compareSettings, mappedFile.FilePath); + } + else + { + FileHolder fileHolder = FileHolder.Get(mappedFile.FilePath.FullName[..^4]); + FilePath filePath = FilePath.Get(resultSettings, metadataSettings, fileHolder, index: null); + if (filePath.Id is null) + return; + id = filePath.Id; + wholePercentages = IMapping.GetWholePercentages(compareSettings, filePath); + } + if (wholePercentages is null) + return; + if (distanceSettings.LinkedAlpha is null && string.IsNullOrEmpty(distanceSettings.LocationContainerDebugDirectory)) + { + if (skipCollection.TryGetValue(id.Value, out List? wholePercentagesCollection)) + { + string checkFile; + FilePath[] filePathMatches = (from l in wholePercentagesCollection where l.WholePercentages == wholePercentages select l.FilePath).ToArray(); + foreach (FilePath filePathMatch in filePathMatches) + { + if (string.IsNullOrEmpty(filePathMatch.FullName) || !File.Exists(filePathMatch.FullName)) + continue; + checkFile = $"{filePathMatch}.dup"; + if (File.Exists(checkFile)) + continue; + File.Move(filePathMatch.FullName, checkFile); + continue; + } + } + } + if (mappedFile.FilePath.Name.EndsWith(lnk) || !File.Exists(mappedFile.FilePath.FullName)) + return; + ExifDirectory exifDirectory = IMetadata.GetExifDirectory(mappedFile.FilePath, mappedFile.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName); + lock (exifDirectories) + exifDirectories.Add(exifDirectory); + } + + internal static ReadOnlyCollection GetMapped(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollections readOnlyCollections) + { + List results = []; + string eDistanceContentDirectory = IResult.GetResultsDateGroupDirectory(resultSettings, nameof(E_Distance), resultSettings.ResultContent); + ReadOnlyCollection records = MappedLogicB.DeleteEmptyDirectoriesAndGetCollection(resultSettings, metadataSettings, peopleSettings, distanceSettings, compareSettings, compare, ticks, eDistanceContentDirectory, readOnlyCollections); + ReadOnlyCollection mappedFiles = GetMappedFiles(resultSettings, metadataSettings, peopleSettings, compareSettings, readOnlyCollections, records); + if (mappedFiles.Count > 0) + { + int maxDegreeOfParallelism = compareSettings.MaxDegreeOfParallelism; + int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); + string message = $") Building Mapped Face Files Collection - {totalSeconds} total second(s)"; + ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; + ReadOnlyDictionary> skipNotSkipCollection = readOnlyCollections.SkipNotSkipCollection; + compare.ConstructProgressBar(mappedFiles.Count, message); + _ = Parallel.For(0, mappedFiles.Count, parallelOptions, (i, state) => + { + compare.Tick(); + MappedParallelFor(resultSettings, metadataSettings, distanceSettings, compareSettings, results, skipNotSkipCollection, mappedFiles[i]); + }); + } + return results.AsReadOnly(); + } + + internal static ReadOnlyDictionary> Extract(ICompareSettings compareSettings, ReadOnlyCollection exifDirectories) + { + Dictionary> results = []; + int? wholePercentages; + Dictionary? keyValues; + Dictionary> keyValuePairs = []; + foreach (ExifDirectory exifDirectory in exifDirectories) + { + if (exifDirectory.FilePath.Id is null) + continue; + if (!keyValuePairs.TryGetValue(exifDirectory.FilePath.Id.Value, out keyValues)) + { + keyValuePairs.Add(exifDirectory.FilePath.Id.Value, []); + if (!keyValuePairs.TryGetValue(exifDirectory.FilePath.Id.Value, out keyValues)) + throw new Exception(); + } + wholePercentages = IMapping.GetWholePercentages(compareSettings, exifDirectory.FilePath); + if (wholePercentages is null) + continue; + keyValues.Add(wholePercentages.Value, exifDirectory.FilePath); + } + foreach (KeyValuePair> keyValuePair in keyValuePairs) + results.Add(keyValuePair.Key, keyValuePair.Value.AsReadOnly()); + return results.AsReadOnly(); + } + +} \ No newline at end of file diff --git a/Distance/Models/Stateless/MappedLogicB.cs b/Distance/Models/Stateless/MappedLogicB.cs new file mode 100644 index 0000000..c74204f --- /dev/null +++ b/Distance/Models/Stateless/MappedLogicB.cs @@ -0,0 +1,500 @@ +using Humanizer; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Globalization; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.Distance.Models.Stateless.Methods; + +internal static class MappedLogicB +{ + + internal record Record(int DirectoryNumber, + bool? IsDefault, + int? LinksCount, + FilePath MappedFaceFilePath, + string? PersonDisplayDirectoryName, + string PersonKeyFormatted); + + internal record TicksDirectory(DateTime AlternateDirectoryDateTime, + string Directory, + DateTime DirectoryDateTime, + string DirectoryName, + bool? IsLocationContainerDebugDirectory, + float? TotalDays); + + private static void MoveTo(string actionDirectory, TicksDirectory ticksDirectory, string directory, string personKeyFormatted, string yearDirectoryName, string alphaDirectoryName, string[] files, string[] facesFileNames) + { + string checkFile; + string actionDirectoryName = Path.GetFileName(actionDirectory); + string checkDirectory = actionDirectoryName.StartsWith("y", StringComparison.CurrentCultureIgnoreCase) ? Path.Combine(ticksDirectory.Directory, personKeyFormatted, yearDirectoryName, alphaDirectoryName) : Path.Combine(directory, actionDirectoryName); + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + foreach (string file in files) + { + if (facesFileNames.Contains(file)) + { + checkFile = Path.Combine(checkDirectory, Path.GetFileName(file)); + if (File.Exists(checkFile)) + continue; + File.Move(file, checkFile); + continue; + } + File.Delete(file); + } + } + + private static void MoveFiles(string personKeyFormatted, string personKeyDirectory, string newestPersonKeyFormatted, string newestPersonKeyDirectory) + { + string[] files; + string checkFile; + string? checkDirectory; + string[] directories = Directory.GetDirectories(personKeyDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string directory in directories) + { + checkDirectory = Path.Combine(newestPersonKeyDirectory, Path.GetFileName(directory)); + if (!Directory.Exists(checkDirectory)) + Directory.Move(directory, checkDirectory); + else + { + files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories); + foreach (string file in files) + { + if (file.Split(personKeyFormatted).Length != 2 || file.Contains(newestPersonKeyFormatted)) + continue; + checkFile = file.Replace(personKeyFormatted, newestPersonKeyFormatted); + checkDirectory = Path.GetDirectoryName(checkFile); + if (checkDirectory is null) + continue; + if (File.Exists(checkFile)) + continue; + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + File.Move(file, checkFile); + } + } + } + _ = IPath.DeleteEmptyDirectories(personKeyDirectory); + } + + private static List UpdateDateVerifyAndGetTicksDirectories(DistanceSettings distanceSettings, string eDistanceContentDirectory) + { + List results = []; + float? totalDays; + long? next = null; + string? checkDirectory; + string ticksDirectoryName; + DateTime directoryDateTime; + DirectoryInfo directoryInfo; + TicksDirectory ticksDirectory; + long? lastDirectoryTicks = null; + DateTime dateTime = DateTime.Now; + DateTime alternateDirectoryDateTime; + bool? isLocationContainerDebugDirectory; + long month = dateTime.AddMonths(1).Ticks - dateTime.Ticks; + for (int i = 1; i < 5; i++) + _ = IPath.DeleteEmptyDirectories(eDistanceContentDirectory); + if (!Directory.Exists(eDistanceContentDirectory)) + _ = Directory.CreateDirectory(eDistanceContentDirectory); + string[] ticksFullPaths = Directory.GetDirectories(eDistanceContentDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string ticksFullPath in ticksFullPaths) + { + ticksDirectoryName = Path.GetFileName(ticksFullPath); + if (ticksDirectoryName.Length < 3) + continue; + if (!long.TryParse(ticksDirectoryName, out long directoryTicks)) + throw new NotSupportedException(); + if (next is null) + next = new DateTime(directoryTicks).Ticks; + else + { + next += month; + checkDirectory = Path.GetDirectoryName(ticksFullPath); + if (string.IsNullOrEmpty(checkDirectory)) + { + if (string.IsNullOrEmpty(checkDirectory)) + continue; + checkDirectory = Path.Combine(checkDirectory, next.Value.ToString()); + if (ticksFullPath == checkDirectory || !checkDirectory.EndsWith(distanceSettings.LocationContainerDirectoryPattern)) + continue; + Directory.Move(ticksFullPath, checkDirectory); + continue; + } + } + directoryInfo = new(ticksFullPath); + directoryDateTime = new DateTime(directoryTicks); + if (directoryInfo.CreationTime.Ticks != directoryTicks) + Directory.SetCreationTime(ticksFullPath, new DateTime(directoryTicks)); + if (directoryInfo.LastWriteTime.Ticks != directoryTicks) + Directory.SetLastWriteTime(ticksFullPath, new DateTime(directoryTicks)); + alternateDirectoryDateTime = new DateTime(directoryDateTime.Year, directoryDateTime.Month, directoryDateTime.Day).AddMonths(1); + isLocationContainerDebugDirectory = distanceSettings.LocationContainerDebugDirectory is null ? null : ticksDirectoryName.EndsWith(distanceSettings.LocationContainerDebugDirectory); + totalDays = lastDirectoryTicks is null || new TimeSpan(dateTime.Ticks - directoryTicks).TotalDays < 1 ? null : (float)new TimeSpan(directoryTicks - lastDirectoryTicks.Value).TotalDays; + ticksDirectory = new(alternateDirectoryDateTime, ticksFullPath, new(directoryTicks), ticksDirectoryName, isLocationContainerDebugDirectory, totalDays); + results.Add(ticksDirectory); + if (directoryDateTime.Hour == 0 && directoryDateTime.Minute == 0 && directoryDateTime.Second == 0) + continue; + lastDirectoryTicks = directoryTicks; + } + string[] compare = (from l in results where l.TotalDays is not null and < 9.95f select l.Directory).ToArray(); + if (compare.Length > 0 && distanceSettings.ReMap) + throw new Exception($"Please Consolidate <{string.Join(Environment.NewLine, compare)}>"); + return results; + } + + private static void Individually(ICompareSettings compareSettings, TicksDirectory ticksDirectory, string directory) + { + bool isDefault; + string[] files; + FileInfo[] collection; + string[] facesFileNames; + string yearDirectoryName; + string[] yearDirectories; + string alphaDirectoryName; + string matchDirectoryName; + string personKeyFormatted; + string[] alphaDirectories; + string[] matchDirectories; + string[] actionDirectories; + string personDisplayDirectory; + string[] personKeyDirectories; + string[] segmentCDirectories = Directory.GetDirectories(directory, "*", SearchOption.TopDirectoryOnly); + foreach (string segmentCDirectory in segmentCDirectories) + { + personKeyDirectories = Directory.GetDirectories(segmentCDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string personKeyDirectory in personKeyDirectories) + { + personKeyFormatted = Path.GetFileName(personKeyDirectory); + yearDirectories = Directory.GetDirectories(personKeyDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string yearDirectory in yearDirectories) + { + yearDirectoryName = Path.GetFileName(yearDirectory); + if (yearDirectoryName.StartsWith('=')) + Directory.Move(yearDirectory, yearDirectory.Replace('=', '~')); + } + yearDirectories = Directory.GetDirectories(personKeyDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string yearDirectory in yearDirectories) + { + yearDirectoryName = Path.GetFileName(yearDirectory); + matchDirectories = Directory.GetDirectories(yearDirectory, "*", SearchOption.TopDirectoryOnly); + alphaDirectories = matchDirectories.Where(l => !long.TryParse(Path.GetFileName(l), out long a)).ToArray(); + if (alphaDirectories.Length == 0) + continue; + alphaDirectoryName = Path.GetFileName(alphaDirectories[0]); + foreach (string matchDirectory in matchDirectories) + { + matchDirectoryName = Path.GetFileName(matchDirectory); + files = Directory.GetFiles(matchDirectory, "*", SearchOption.TopDirectoryOnly); + if (files.Length != 4) + continue; + collection = files.Select(l => new FileInfo(l)).ToArray(); + isDefault = IPerson.IsDefaultName(alphaDirectoryName) && IPersonBirthday.IsCounterPersonYear(personKeyFormatted[..4]); + if (isDefault) + facesFileNames = (from l in collection where l.Extension == compareSettings.FacesFileNameExtension select l.FullName).ToArray(); + else + facesFileNames = (from l in collection where l.Extension == compareSettings.FacesFileNameExtension && l.Name.Contains(matchDirectoryName) select l.FullName).ToArray(); + if (facesFileNames.Length == 0) + continue; + personDisplayDirectory = Path.Combine(matchDirectory, alphaDirectoryName); + if (!Directory.Exists(personDisplayDirectory) || !Directory.Exists(matchDirectory)) + continue; + _ = Process.Start("explorer", matchDirectory); + for (int i = 0; i < int.MaxValue; i++) + { + Thread.Sleep(500); + actionDirectories = Directory.GetDirectories(matchDirectory, "*", SearchOption.TopDirectoryOnly).Where(l => l != personDisplayDirectory && !l.EndsWith("Maybe")).ToArray(); + if (actionDirectories.Length > 0) + { + MoveTo(actionDirectories[0], ticksDirectory, directory, personKeyFormatted, yearDirectoryName, alphaDirectoryName, files, facesFileNames); + break; + } + } + } + } + } + } + } + + private static List GetRecords(ResultSettings resultSettings, MetadataSettings metadataSettings, ICompareSettings compareSettings, bool? isDefault, string[] files, int directoryNumber, string personKeyFormatted, int? linksCount, List distinct, string? personDisplayDirectoryName) + { + List results = []; + Record record; + string fileName; + string checkFile; + FilePath filePath; + FileHolder fileHolder; + int? wholePercentages; + foreach (string file in files) + { + if (file.EndsWith(".lnk")) + continue; + fileHolder = FileHolder.Get(file); + filePath = FilePath.Get(resultSettings, metadataSettings, fileHolder, index: null); + if (filePath.Id is null) + continue; + wholePercentages = IMapping.GetWholePercentages(compareSettings, filePath); + if (wholePercentages is null) + continue; + fileName = Path.GetFileName(file); + if (distinct.Contains(fileName)) + { + checkFile = $"{file}.dup"; + if (File.Exists(checkFile)) + continue; + File.Move(file, checkFile); + continue; + } + distinct.Add(fileName); + record = new(directoryNumber, isDefault, linksCount, filePath, personDisplayDirectoryName, personKeyFormatted); + results.Add(record); + } + return results; + } + + private static string[] RenameBirth(string[] files) + { + List results = []; + string checkFile; + foreach (string file in files) + { + if (file.EndsWith(".brt")) + { + results.Add(file); + continue; + } + checkFile = $"{file}.brt"; + if (File.Exists(checkFile)) + { + results.Add(file); + continue; + } + File.Move(file, checkFile); + results.Add(checkFile); + } + return results.ToArray(); + } + + private static void MovedToNewestPersonKeyFormatted(string personKeyFormatted, string newestPersonKeyFormatted, TicksDirectory ticksDirectory, string personKeyDirectory) + { + string newestPersonKeyDirectory = Path.Combine(ticksDirectory.Directory, newestPersonKeyFormatted); + if (Directory.Exists(newestPersonKeyDirectory)) + MoveFiles(personKeyFormatted, personKeyDirectory, newestPersonKeyFormatted, newestPersonKeyDirectory); + else + Directory.Move(personKeyDirectory, newestPersonKeyDirectory); + } + + private static int? GetLinksCount(string yearDirectory) + { + int? result; + string[] yearDirectoryNameSegments = Path.GetFileName(yearDirectory).Split('-'); + if (yearDirectoryNameSegments.Length != 3) + result = null; + else + { + string lastSegment = yearDirectoryNameSegments[^1]; + if (lastSegment.Length != 3 || !lastSegment.All(l => l == lastSegment[0])) + result = null; + else + result = lastSegment[0] - 65; + } + return result; + } + + internal static ReadOnlyCollection DeleteEmptyDirectoriesAndGetCollection(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, string eDistanceContentDirectory, ReadOnlyCollections readOnlyCollections) + { + List results = []; + bool check; + string message; + string[] files; + bool? isDefault; + int? linksCount; + int totalSeconds; + DateTime dateTime; + TimeSpan timeSpan; + int directoryNumber; + string? checkDirectory; + string[] yearDirectories; + string personKeyFormatted; + List distinct = []; + string? personFirstInitial; + bool isReservedDirectoryName; + string[] personNameDirectories; + string? newestPersonKeyFormatted; + string? personDisplayDirectoryName; + string[] personNameLinkDirectories; + string? personFirstInitialDirectory; + List ticksDirectories; + string[] personKeyFormattedDirectories; + string manualCopyHumanized = nameof(Shared.Models.Stateless.IMapLogic.ManualCopy).Humanize(LetterCasing.Title); + string forceSingleImageHumanized = nameof(Shared.Models.Stateless.IMapLogic.ForceSingleImage).Humanize(LetterCasing.Title); + for (int i = 1; i < 6; i++) + { + check = false; + results.Clear(); + distinct.Clear(); + directoryNumber = 0; + ticksDirectories = UpdateDateVerifyAndGetTicksDirectories(distanceSettings, eDistanceContentDirectory); + totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); + message = $"{i}) {ticksDirectories.Count:000} compile from and clean ticks Director(ies) - B - {totalSeconds} total second(s)"; + compare.ConstructProgressBar(ticksDirectories.Count, message); + foreach (TicksDirectory ticksDirectory in ticksDirectories) + { + if (i == 1) + compare.Tick(); + personKeyFormattedDirectories = Directory.GetDirectories(ticksDirectory.Directory, "*", SearchOption.TopDirectoryOnly); + foreach (string personKeyFormattedDirectory in personKeyFormattedDirectories) + { + personKeyFormatted = Path.GetFileName(personKeyFormattedDirectory); + isReservedDirectoryName = personKeyFormatted.StartsWith(nameof(Shared.Models.Stateless.IMapLogic.Sorting)) || personKeyFormatted.StartsWith(nameof(Shared.Models.Stateless.IMapLogic.Mapping)) || personKeyFormatted.StartsWith(nameof(Shared.Models.Stateless.IMapLogic.ManualCopy)); + if (!isReservedDirectoryName && personKeyFormatted.StartsWith(nameof(Shared.Models.Stateless.IMapLogic.Individually))) + { + Individually(compareSettings, ticksDirectory, personKeyFormattedDirectory); + throw new Exception($"B) Move personKey directories up one from {nameof(Shared.Models.Stateless.IMapLogic.Sorting)} and delete {nameof(Shared.Models.Stateless.IMapLogic.Sorting)} directory!"); + } + _ = readOnlyCollections.PersonKeyFormattedToNewestPersonKeyFormatted.TryGetValue(personKeyFormatted, out newestPersonKeyFormatted); + if (readOnlyCollections.PersonKeyFormattedToNewestPersonKeyFormatted.Count > 0 && newestPersonKeyFormatted is null) + { + timeSpan = new TimeSpan(DateTime.Now.Ticks - ticksDirectory.DirectoryDateTime.Ticks); + if (timeSpan.TotalDays > 6) + throw new Exception($"{distanceSettings.MappingDefaultName} <{ticksDirectory.DirectoryDateTime}> are only allowed within x days!"); + } + yearDirectories = Directory.GetDirectories(personKeyFormattedDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string yearDirectory in yearDirectories) + { + if (check && !Directory.Exists(yearDirectory)) + continue; + if (ticksDirectory.IsLocationContainerDebugDirectory is null || !ticksDirectory.IsLocationContainerDebugDirectory.Value) + linksCount = null; + else + linksCount = GetLinksCount(yearDirectory); + if (ticksDirectory.DirectoryName != distanceSettings.LocationContainerDebugDirectory) + { + files = Directory.GetFiles(yearDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string file in files) + File.Delete(file); + } + if (ticksDirectory.DirectoryName == distanceSettings.LocationContainerDebugDirectory) + { + isDefault = null; + personDisplayDirectoryName = null; + files = Directory.GetFiles(yearDirectory, "*", SearchOption.TopDirectoryOnly); + results.AddRange(GetRecords(resultSettings, metadataSettings, compareSettings, isDefault, files, directoryNumber, personKeyFormatted, linksCount, distinct, personDisplayDirectoryName)); + files = Directory.GetFiles(yearDirectory, "*.lnk", SearchOption.AllDirectories); + foreach (string file in files) + File.Delete(file); + continue; + } + personNameDirectories = Directory.GetDirectories(yearDirectory, "*", SearchOption.TopDirectoryOnly); + if (personNameDirectories.Length > 1) + throw new NotSupportedException("Try deleting *.lnk files!"); + foreach (string personNameDirectory in personNameDirectories) + { + directoryNumber++; + personDisplayDirectoryName = Path.GetFileName(personNameDirectory); + isDefault = IPerson.IsDefaultName(personDisplayDirectoryName) && IPersonBirthday.IsCounterPersonYear(personKeyFormatted[..4]); + if (isDefault.Value && personDisplayDirectoryName.Length == 1) + { + if (personKeyFormatted.Length != peopleSettings.PersonBirthdayFormat.Length || !DateTime.TryParseExact(personKeyFormatted, peopleSettings.PersonBirthdayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime)) + continue; + checkDirectory = Path.Combine(yearDirectory, $"X+{dateTime.Ticks}"); + if (Directory.Exists(checkDirectory)) + { + Directory.Delete(yearDirectory, recursive: true); + continue; + } + Directory.Move(personNameDirectory, checkDirectory); + if (!check) + check = true; + continue; + } + if (isDefault.Value && (ticksDirectory.DirectoryDateTime.Hour != 0 || ticksDirectory.DirectoryDateTime.Minute != 0 || ticksDirectory.DirectoryDateTime.Second != 0)) + { + checkDirectory = Path.GetDirectoryName(ticksDirectory.Directory); + if (checkDirectory is null) + continue; + checkDirectory = Path.Combine(checkDirectory, ticksDirectory.AlternateDirectoryDateTime.Ticks.ToString()); + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + checkDirectory = Path.Combine(checkDirectory, personKeyFormatted); + if (!Directory.Exists(checkDirectory)) + { + Directory.Move(personKeyFormattedDirectory, checkDirectory); + if (!check) + check = true; + break; + } + } + files = Directory.GetFiles(personNameDirectory, "*", SearchOption.TopDirectoryOnly); + if (isReservedDirectoryName && files.Length > 0) + throw new Exception($"Move personKey directories up one from {nameof(Shared.Models.Stateless.IMapLogic.Sorting)} and delete {nameof(Shared.Models.Stateless.IMapLogic.Sorting)} directory!"); + if (personKeyFormatted == manualCopyHumanized && files.Length > 0) + throw new Exception($"Move personKey directories up one from {manualCopyHumanized} and delete {manualCopyHumanized} directory!"); + if (personKeyFormatted == forceSingleImageHumanized && files.Length > 0) + throw new Exception($"Move personKey directories up one from {forceSingleImageHumanized} and delete {forceSingleImageHumanized} directory!"); + if (!isDefault.Value) + { + if (readOnlyCollections.PersonKeyFormattedToNewestPersonKeyFormatted.Count > 0 && newestPersonKeyFormatted is null) + files = RenameBirth(files); + else if (newestPersonKeyFormatted is not null && personKeyFormatted != newestPersonKeyFormatted) + { + if (!check) + check = true; + MovedToNewestPersonKeyFormatted(personKeyFormatted, newestPersonKeyFormatted, ticksDirectory, personKeyFormattedDirectory); + continue; + } + } + if (personKeyFormatted.Length != peopleSettings.PersonBirthdayFormat.Length) + continue; + if (personDisplayDirectoryName.Length == 1 || isDefault.Value || !readOnlyCollections.PersonKeyFormattedCollection.Contains(personKeyFormatted)) + personFirstInitialDirectory = personNameDirectory; + else + { + personFirstInitial = personDisplayDirectoryName[..1]; + if (personFirstInitial.All(char.IsDigit)) + { + foreach (string file in files) + File.Delete(file); + files = Directory.GetFiles(personNameDirectory, "*", SearchOption.AllDirectories); + foreach (string file in files) + File.Delete(file); + _ = IPath.DeleteEmptyDirectories(personNameDirectory); + continue; + } + personFirstInitialDirectory = Path.Combine(yearDirectory, personFirstInitial.ToString()); + if (Directory.Exists(personFirstInitialDirectory)) + throw new Exception("Forgot to ..."); + Directory.Move(personNameDirectory, personFirstInitialDirectory); + files = Directory.GetFiles(personFirstInitialDirectory, "*", SearchOption.TopDirectoryOnly); + } + results.AddRange(GetRecords(resultSettings, metadataSettings, compareSettings, isDefault, files, directoryNumber, personKeyFormatted, linksCount, distinct, personDisplayDirectoryName)); + personNameLinkDirectories = Directory.GetDirectories(personFirstInitialDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string personNameLinkDirectory in personNameLinkDirectories) + { + files = Directory.GetFiles(personNameLinkDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string file in files) + { + if (!file.EndsWith(".lnk")) + continue; + File.Delete(file); + } + _ = IPath.DeleteEmptyDirectories(personNameLinkDirectory); + } + _ = IPath.DeleteEmptyDirectories(personFirstInitialDirectory); + } + _ = IPath.DeleteEmptyDirectories(yearDirectory); + } + _ = IPath.DeleteEmptyDirectories(personKeyFormattedDirectory); + } + _ = IPath.DeleteEmptyDirectories(ticksDirectory.Directory); + _ = IPath.DeleteEmptyDirectories(ticksDirectory.Directory); + } + if (check) + continue; + break; + } + return results.AsReadOnly(); + } + +} \ No newline at end of file diff --git a/Distance/Models/Stateless/Methods/IDistance.cs b/Distance/Models/Stateless/Methods/IDistance.cs new file mode 100644 index 0000000..89a0d2a --- /dev/null +++ b/Distance/Models/Stateless/Methods/IDistance.cs @@ -0,0 +1,57 @@ +using System.Collections.ObjectModel; +using View_by_Distance.Metadata.Models; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.Distance.Models.Stateless.Methods; + +public interface IDistance +{ + + static ReadOnlyCollection TestStatic_GetMapped(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollections readOnlyCollections) => + GetMapped(resultSettings, metadataSettings, peopleSettings, distanceSettings, compareSettings, compare, ticks, readOnlyCollections); + static ReadOnlyCollection GetMapped(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollections readOnlyCollections) => + MappedLogicA.GetMapped(resultSettings, metadataSettings, peopleSettings, distanceSettings, compareSettings, compare, ticks, readOnlyCollections); + + static ReadOnlyDictionary> TestStatic_Extract(ICompareSettings compareSettings, ReadOnlyCollection exifDirectories) => + Extract(compareSettings, exifDirectories); + static ReadOnlyDictionary> Extract(ICompareSettings compareSettings, ReadOnlyCollection exifDirectories) => + MappedLogicA.Extract(compareSettings, exifDirectories); + + static ReadOnlyCollection TestStatic_GetMappedExifDirectoryWithEncoding(ICompare compare, long ticks, ReadOnlyCollection exifDirectories) => + GetMappedExifDirectoryWithEncoding(compare, ticks, exifDirectories); + static ReadOnlyCollection GetMappedExifDirectoryWithEncoding(ICompare compare, long ticks, ReadOnlyCollection exifDirectories) => + FaceEncodingLogic.GetMappedExifDirectoryWithEncoding(compare, ticks, exifDirectories); + + static ReadOnlyCollection TestStatic_GetPreFilterLocationContainer(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollections readOnlyCollections, ReadOnlyDictionary> keyValuePairs, ReadOnlyCollection exifDirectories) => + GetPreFilterLocationContainer(distanceSettings, compareSettings, compare, ticks, readOnlyCollections, keyValuePairs, exifDirectories); + static ReadOnlyCollection GetPreFilterLocationContainer(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollections readOnlyCollections, ReadOnlyDictionary> keyValuePairs, ReadOnlyCollection exifDirectories) => + FilterLogicA.GetPreFilterLocationContainer(distanceSettings, compareSettings, compare, ticks, readOnlyCollections, keyValuePairs, exifDirectories); + + static ReadOnlyCollection TestStatic_GetPostFilterLocationContainer(ReadOnlyCollection preFiltered, DistanceLimits distanceLimits) => + GetPostFilterLocationContainer(preFiltered, distanceLimits); + static ReadOnlyCollection GetPostFilterLocationContainer(ReadOnlyCollection preFiltered, DistanceLimits distanceLimits) => + FilterLogicC.GetPostFilterLocationContainer(preFiltered, distanceLimits); + + static ReadOnlyCollection TestStatic_GetMatrixLocationContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollection mappedExifDirectoryWithEncoding, DistanceLimits distanceLimits, ReadOnlyCollection postFiltered) => + GetMatrixLocationContainers(distanceSettings, compareSettings, compare, ticks, mappedExifDirectoryWithEncoding, distanceLimits, postFiltered); + static ReadOnlyCollection GetMatrixLocationContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollection mappedExifDirectoryWithEncoding, DistanceLimits distanceLimits, ReadOnlyCollection postFiltered) => + FilterLogicC.GetMatrixLocationContainers(distanceSettings, compareSettings, compare, ticks, mappedExifDirectoryWithEncoding, distanceLimits, postFiltered); + + static ReadOnlyDictionary TestStatic_GetOnlyOne(DistanceSettings distanceSettings, ReadOnlyCollection matrix) => + GetOnlyOne(distanceSettings, matrix); + static ReadOnlyDictionary GetOnlyOne(DistanceSettings distanceSettings, ReadOnlyCollection matrix) => + FilterLogicC.GetOnlyOne(distanceSettings, matrix); + + static ReadOnlyCollection TestStatic_GetSaveContainers(ResultSettings resultSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, string outputResolution, ReadOnlyDictionary onlyOne) => + GetSaveContainers(resultSettings, distanceSettings, compareSettings, compare, ticks, outputResolution, onlyOne); + static ReadOnlyCollection GetSaveContainers(ResultSettings resultSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, string outputResolution, ReadOnlyDictionary onlyOne) => + FilterLogicD.GetSaveContainers(resultSettings, distanceSettings, compareSettings, compare, ticks, outputResolution, onlyOne); + + static void TestStatic_SaveContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, int? updated, ReadOnlyCollection saveContainers) => + SaveContainers(distanceSettings, compareSettings, compare, ticks, updated, saveContainers); + static void SaveContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, int? updated, ReadOnlyCollection saveContainers) => + FilterLogicD.SaveContainers(distanceSettings, compareSettings, compare, ticks, updated, saveContainers); + +} \ No newline at end of file diff --git a/Face/AA.Face.csproj b/Face/AA.Face.csproj new file mode 100644 index 0000000..624b872 --- /dev/null +++ b/Face/AA.Face.csproj @@ -0,0 +1,44 @@ + + + enable + enable + library + win-x64 + net9.0 + + + Phares.AA.Face + false + 9.0.100.0 + Mike Phares + Phares + true + snupkg + + + true + true + true + + + Windows + + + OSX + + + Linux + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Face/Models/D_Face.cs b/Face/Models/D_Face.cs new file mode 100644 index 0000000..6e13fdf --- /dev/null +++ b/Face/Models/D_Face.cs @@ -0,0 +1,3 @@ +namespace View_by_Distance.Metadata.Models; + +public class D_Face() { } \ No newline at end of file diff --git a/Face/Models/Stateless/Face.cs b/Face/Models/Stateless/Face.cs new file mode 100644 index 0000000..3e038bc --- /dev/null +++ b/Face/Models/Stateless/Face.cs @@ -0,0 +1,53 @@ +using System.Collections.ObjectModel; +using View_by_Distance.Metadata.Models; +using View_by_Distance.Metadata.Models.Stateless.Methods; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.Face.Models.Stateless.Methods; + +internal static class Face +{ + + private static void ExifDirectoriesParallelFor(ResultSettings resultSettings, MetadataSettings metadataSettings, List results, FileInfo fileInfo) + { + FileHolder fileHolder = FileHolder.Get(fileInfo, id: null); + const PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName = null; + FilePath filePath = FilePath.Get(resultSettings, metadataSettings, fileHolder, index: null); + ExifDirectory exifDirectory = IMetadata.GetExifDirectory(filePath, personKeyFormattedAndKeyTicksAndDisplayDirectoryName); + results.Add(exifDirectory); + } + + internal static ReadOnlyCollection GetExifDirectories(ResultSettings resultSettings, MetadataSettings metadataSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, string outputResolution) + { + List results = []; + FileInfo fileInfo; + int maxDegreeOfParallelism = compareSettings.MaxDegreeOfParallelism; + ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; + long? skipOlderThan = distanceSettings.SkipOlderThanDays < 1 ? null : new DateTime(ticks).AddDays(-distanceSettings.SkipOlderThanDays).Ticks; + string resultsFullGroupDirectory = Path.GetFullPath(IResult.GetResultsFullGroupDirectory(resultSettings, + nameof(D_Face), + outputResolution, + includeResizeGroup: true, + includeModel: true, + includePredictorModel: true)); + string contentDirectory = Path.Combine(resultsFullGroupDirectory, resultSettings.ResultContent); + if (!Directory.Exists(contentDirectory)) + _ = Directory.CreateDirectory(contentDirectory); + string collectionDirectory = Path.Combine(resultsFullGroupDirectory, resultSettings.ResultCollection); + if (!Directory.Exists(collectionDirectory)) + _ = Directory.CreateDirectory(collectionDirectory); + string[] files = Directory.GetFiles(contentDirectory, $"*{compareSettings.FacesFileNameExtension}", SearchOption.AllDirectories); + compare.ConstructProgressBar(files.Length, $"{nameof(D_Face)}.{nameof(GetExifDirectories)}"); + _ = Parallel.For(0, files.Length, parallelOptions, (i, state) => + { + compare.Tick(); + fileInfo = new(files[i]); + if (skipOlderThan is null || (fileInfo.Exists && fileInfo.LastWriteTime.Ticks >= skipOlderThan.Value)) + ExifDirectoriesParallelFor(resultSettings, metadataSettings, results, fileInfo); + }); + return results.AsReadOnly(); + } + +} \ No newline at end of file diff --git a/Face/Models/Stateless/Methods/IFace.cs b/Face/Models/Stateless/Methods/IFace.cs new file mode 100644 index 0000000..04c2962 --- /dev/null +++ b/Face/Models/Stateless/Methods/IFace.cs @@ -0,0 +1,16 @@ +using System.Collections.ObjectModel; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.Face.Models.Stateless.Methods; + +public interface IFace +{ + + ReadOnlyCollection TestStatic_GetExifDirectories(ResultSettings resultSettings, MetadataSettings metadataSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, string outputResolution) => + GetExifDirectories(resultSettings, metadataSettings, distanceSettings, compareSettings, compare, ticks, outputResolution); + static ReadOnlyCollection GetExifDirectories(ResultSettings resultSettings, MetadataSettings metadataSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, string outputResolution) => + Face.GetExifDirectories(resultSettings, metadataSettings, distanceSettings, compareSettings, compare, ticks, outputResolution); + +} \ No newline at end of file diff --git a/FaceRecognitionDotNet/AA.FaceRecognitionDotNet.csproj b/FaceRecognitionDotNet/AA.FaceRecognitionDotNet.csproj new file mode 100644 index 0000000..96036da --- /dev/null +++ b/FaceRecognitionDotNet/AA.FaceRecognitionDotNet.csproj @@ -0,0 +1,51 @@ + + + enable + enable + library + win-x64 + net9.0 + + + Phares.AA.FaceRecognitionDotNet + false + 9.0.100.1 + Mike Phares + Phares + true + snupkg + + + true + + + true + true + true + + + Windows + + + OSX + + + Linux + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FaceRecognitionDotNet/DisposableObject.cs b/FaceRecognitionDotNet/DisposableObject.cs new file mode 100644 index 0000000..9a37cbc --- /dev/null +++ b/FaceRecognitionDotNet/DisposableObject.cs @@ -0,0 +1,97 @@ +namespace View_by_Distance.FaceRecognitionDotNet; + +/// +/// Represents a class which has managed or unmanaged resources. +/// +public abstract class DisposableObject : IDisposable +{ + + #region Properties + + /// + /// Gets a value indicating whether this instance has been disposed. + /// + /// true if this instance has been disposed; otherwise, false. + public bool IsDisposed + { + get; + private set; + + } + + #endregion + + #region Methods + + /// + /// If this object is disposed, then is thrown. + /// + public void ThrowIfDisposed() => + ObjectDisposedException.ThrowIf(IsDisposed, this); + + internal void ThrowIfDisposed(string objectName) + { +#pragma warning disable CA1513 + if (IsDisposed) + throw new ObjectDisposedException(objectName); +#pragma warning restore CA1513 + } + + #region Overrides + + /// + /// Releases all managed resources. + /// + protected virtual void DisposeManaged() + { + + } + + /// + /// Releases all unmanaged resources. + /// + protected virtual void DisposeUnmanaged() + { + + } + + #endregion + + #endregion + + #region IDisposable Members + + /// + /// Releases all resources used by this . + /// + public void Dispose() + { + GC.SuppressFinalize(this); + + Dispose(true); + } + + /// + /// Releases all resources used by this . + /// + /// Indicate value whether method was called. + private void Dispose(bool disposing) + { + if (IsDisposed) + { + return; + + } + + IsDisposed = true; + + if (disposing) + + DisposeManaged(); + + DisposeUnmanaged(); + } + + #endregion + +} \ No newline at end of file diff --git a/FaceRecognitionDotNet/Dlib/Python/CnnFaceDetectionModelV1.cs b/FaceRecognitionDotNet/Dlib/Python/CnnFaceDetectionModelV1.cs new file mode 100644 index 0000000..0cf15e8 --- /dev/null +++ b/FaceRecognitionDotNet/Dlib/Python/CnnFaceDetectionModelV1.cs @@ -0,0 +1,111 @@ +using DlibDotNet; +using DlibDotNet.Dnn; +using View_by_Distance.Shared.Models.Stateless; + +namespace View_by_Distance.FaceRecognitionDotNet.Dlib.Python; + +internal sealed class CnnFaceDetectionModelV1 +{ + + #region Methods + + public static IEnumerable Detect(LossMmod net, Image image, int upsampleNumTimes) + { + using PyramidDown? pyr = new(2); + List? rects = []; + + // Copy the data into dlib based objects + using Matrix? matrix = new(); + Mode type = image.Mode; + switch (type) + { + case Mode.Greyscale: + case Mode.Rgb: + DlibDotNet.Dlib.AssignImage(image.Matrix, matrix); + break; + default: + throw new NotSupportedException("Unsupported image type, must be 8bit gray or RGB image."); + } + + // Upsampling the image will allow us to detect smaller faces but will cause the + // program to use more RAM and run longer. + int levels = upsampleNumTimes; + while (levels > 0) + { + levels--; + DlibDotNet.Dlib.PyramidUp(matrix, 2); + } + + OutputLabels>? dets = net.Operator(matrix); + + // Scale the detection locations back to the original image size + // if the image was upscaled. + foreach (MModRect? d in dets.First()) + { + DRectangle drect = pyr.RectDown(new DRectangle(d.Rect), (uint)upsampleNumTimes); + d.Rect = new Rectangle((int)drect.Left, (int)drect.Top, (int)drect.Right, (int)drect.Bottom); + rects.Add(d); + } + + return rects; + } + + public static IEnumerable> DetectMulti(LossMmod net, IEnumerable images, int upsampleNumTimes, int batchSize = 128) + { + List>? destImages = []; + List>? allRects = []; + + try + { + using PyramidDown? pyr = new(2); + // Copy the data into dlib based objects + foreach (Image? image in images) + { + Matrix? matrix = new(); + Mode type = image.Mode; + switch (type) + { + case Mode.Greyscale: + case Mode.Rgb: + DlibDotNet.Dlib.AssignImage(image.Matrix, matrix); + break; + default: + throw new NotSupportedException("Unsupported image type, must be 8bit gray or RGB image."); + } + + for (int i = 0; i < upsampleNumTimes; i++) + DlibDotNet.Dlib.PyramidUp(matrix); + + destImages.Add(matrix); + } + + for (int i = 1; i < destImages.Count; i++) + if (destImages[i - 1].Columns != destImages[i].Columns || destImages[i - 1].Rows != destImages[i].Rows) + throw new ArgumentException("Images in list must all have the same dimensions."); + + OutputLabels>? dets = net.Operator(destImages, (ulong)batchSize); + foreach (IEnumerable? det in dets) + { + List? rects = []; + foreach (MModRect? d in det) + { + DRectangle drect = pyr.RectDown(new DRectangle(d.Rect), (uint)upsampleNumTimes); + d.Rect = new Rectangle((int)drect.Left, (int)drect.Top, (int)drect.Right, (int)drect.Bottom); + rects.Add(d); + } + + allRects.Add(rects); + } + } + finally + { + foreach (Matrix? matrix in destImages) + matrix.Dispose(); + } + + return allRects; + } + + #endregion + +} \ No newline at end of file diff --git a/FaceRecognitionDotNet/Dlib/Python/FaceRecognitionModelV1.cs b/FaceRecognitionDotNet/Dlib/Python/FaceRecognitionModelV1.cs new file mode 100644 index 0000000..3f6d155 --- /dev/null +++ b/FaceRecognitionDotNet/Dlib/Python/FaceRecognitionModelV1.cs @@ -0,0 +1,129 @@ +using DlibDotNet; +using DlibDotNet.Dnn; + +namespace View_by_Distance.FaceRecognitionDotNet.Dlib.Python; + +internal sealed class FaceRecognitionModelV1 +{ + + #region Methods + + public static Matrix ComputeFaceDescriptor(LossMetric net, Image img, FullObjectDetection face, int numberOfJitters) + { + FullObjectDetection[]? faces = [face]; + return ComputeFaceDescriptors(net, img, faces, numberOfJitters).First(); + } + + public static IEnumerable> ComputeFaceDescriptors(LossMetric net, Image img, IEnumerable faces, int numberOfJitters) + { + Image[]? batchImage = [img]; + IEnumerable[]? batchFaces = [faces]; + return BatchComputeFaceDescriptors(net, batchImage, batchFaces, numberOfJitters).First(); + } + + public static IEnumerable>> BatchComputeFaceDescriptors(LossMetric net, + IList batchImages, + IList> batchFaces, + int numberOfJitters) + { + if (batchImages.Count != batchFaces.Count) + throw new ArgumentException("The array of images and the array of array of locations must be of the same size"); + + foreach (IEnumerable? faces in batchFaces) + foreach (FullObjectDetection? f in faces) + { + if (f.Parts is not 68 and not 5) + throw new ArgumentException("The full_object_detection must use the iBUG 300W 68 point face landmark style or dlib's 5 point style."); + } + + List>>? faceChipsArray = new(batchImages.Count); + List>? faceChips = []; + for (int i = 0; i < batchImages.Count; ++i) + { + IEnumerable? faces = batchFaces[i]; + Image? img = batchImages[i]; + + List? dets = new(faces.Count()); + foreach (FullObjectDetection? f in faces) + dets.Add(DlibDotNet.Dlib.GetFaceChipDetails(f, 150, 0.25)); + + Array>? thisImageFaceChips = DlibDotNet.Dlib.ExtractImageChips(img.Matrix, dets); + foreach (Matrix? chip in thisImageFaceChips) + faceChips.Add(chip); + faceChipsArray.Add(thisImageFaceChips); + + foreach (ChipDetails? det in dets) + det.Dispose(); + } + + List>>? faceDescriptors = []; + for (int i = 0, count = batchImages.Count; i < count; i++) + faceDescriptors.Add([]); + + if (numberOfJitters <= 1) + { + // extract descriptors and convert from float vectors to double vectors + OutputLabels>? descriptors = net.Operator(faceChips, 16); + int index = 0; + Matrix[]? list = descriptors.Select(matrix => matrix).ToArray(); + for (int i = 0; i < batchFaces.Count; ++i) + for (int j = 0; j < batchFaces[i].Count(); ++j) + faceDescriptors[i].Add(DlibDotNet.Dlib.MatrixCast(list[index++])); + + if (index != list.Length) + throw new ApplicationException(); + } + else + { + // extract descriptors and convert from float vectors to double vectors + int index = 0; + for (int i = 0; i < batchFaces.Count; ++i) + for (int j = 0; j < batchFaces[i].Count(); ++j) + { + Matrix[]? tmp = JitterImage(faceChips[index++], numberOfJitters).ToArray(); + using (OutputLabels>? tmp2 = net.Operator(tmp, 16)) + using (MatrixOp? mat = DlibDotNet.Dlib.Mat(tmp2)) + { + Matrix? r = DlibDotNet.Dlib.Mean(mat); + faceDescriptors[i].Add(r); + } + + foreach (Matrix? matrix in tmp) + matrix.Dispose(); + } + + if (index != faceChips.Count) + throw new ApplicationException(); + } + + if (faceChipsArray.Count > 0) + { + foreach (Array>? array in faceChipsArray) + { + foreach (Matrix? faceChip in array) + faceChip.Dispose(); + array.Dispose(); + } + } + + return faceDescriptors; + } + + #region Helpers + + private static readonly Rand _Rand = new(); + + private static IEnumerable> JitterImage(Matrix img, int numberOfJitters) + { + List>? crops = []; + for (int i = 0; i < numberOfJitters; ++i) + crops.Add(DlibDotNet.Dlib.JitterImage(img, _Rand)); + + return crops; + } + + #endregion + + #endregion + +} \ No newline at end of file diff --git a/FaceRecognitionDotNet/Dlib/Python/SimpleObjectDetector.cs b/FaceRecognitionDotNet/Dlib/Python/SimpleObjectDetector.cs new file mode 100644 index 0000000..4f6b544 --- /dev/null +++ b/FaceRecognitionDotNet/Dlib/Python/SimpleObjectDetector.cs @@ -0,0 +1,169 @@ +using DlibDotNet; +using View_by_Distance.Shared.Models.Stateless; + +namespace View_by_Distance.FaceRecognitionDotNet.Dlib.Python; + +internal sealed class SimpleObjectDetector +{ + + #region Methods + + public static IEnumerable RunDetectorWithUpscale1(FrontalFaceDetector detector, + Image img, + uint upsamplingAmount, + double adjustThreshold, + List detectionConfidences, + List weightIndices) + { + List? rectangles = []; + + if (img.Mode == Mode.Greyscale) + { + Matrix? greyscaleMatrix = img.Matrix as Matrix; + if (upsamplingAmount == 0) + { + detector.Operator(greyscaleMatrix, out IEnumerable? rectDetections, adjustThreshold); + + RectDetection[]? dets = rectDetections.ToArray(); + SplitRectDetections(dets, rectangles, detectionConfidences, weightIndices); + + foreach (RectDetection? rectDetection in dets) + rectDetection.Dispose(); + } + else + { + using PyramidDown? pyr = new(2); + Matrix? temp = null; + + try + { + DlibDotNet.Dlib.PyramidUp(greyscaleMatrix, pyr, out temp); + + uint levels = upsamplingAmount - 1; + while (levels > 0) + { + levels--; + DlibDotNet.Dlib.PyramidUp(temp); + } + + detector.Operator(temp, out IEnumerable? rectDetections, adjustThreshold); + + RectDetection[]? dets = rectDetections.ToArray(); + foreach (RectDetection? t in dets) + t.Rect = pyr.RectDown(t.Rect, upsamplingAmount); + + SplitRectDetections(dets, rectangles, detectionConfidences, weightIndices); + + foreach (RectDetection? rectDetection in dets) + rectDetection.Dispose(); + } + finally + { + temp?.Dispose(); + } + } + + return rectangles; + } + else + { + Matrix? rgbMatrix = img.Matrix as Matrix; + if (upsamplingAmount == 0) + { + detector.Operator(rgbMatrix, out IEnumerable? rectDetections, adjustThreshold); + + RectDetection[]? dets = rectDetections.ToArray(); + SplitRectDetections(dets, rectangles, detectionConfidences, weightIndices); + + foreach (RectDetection? rectDetection in dets) + rectDetection.Dispose(); + } + else + { + using PyramidDown? pyr = new(2); + Matrix? temp = null; + + try + { + DlibDotNet.Dlib.PyramidUp(rgbMatrix, pyr, out temp); + + uint levels = upsamplingAmount - 1; + while (levels > 0) + { + levels--; + DlibDotNet.Dlib.PyramidUp(temp); + } + + detector.Operator(temp, out IEnumerable? rectDetections, adjustThreshold); + + RectDetection[]? dets = rectDetections.ToArray(); + foreach (RectDetection? t in dets) + t.Rect = pyr.RectDown(t.Rect, upsamplingAmount); + + SplitRectDetections(dets, rectangles, detectionConfidences, weightIndices); + + foreach (RectDetection? rectDetection in dets) + rectDetection.Dispose(); + } + finally + { + temp?.Dispose(); + } + } + + return rectangles; + } + } + + public static IEnumerable> RunDetectorWithUpscale2(FrontalFaceDetector detector, + Image image, + uint upsamplingAmount) + { + if (detector == null) + throw new NullReferenceException(nameof(detector)); + if (image == null) + throw new NullReferenceException(nameof(image)); + + detector.ThrowIfDisposed(); + image.ThrowIfDisposed(); + + List? detectionConfidences = []; + List? weightIndices = []; + const double adjustThreshold = 0.0; + + Rectangle[]? rects = RunDetectorWithUpscale1(detector, + image, + upsamplingAmount, + adjustThreshold, + detectionConfidences, + weightIndices).ToArray(); + + int index = 0; + foreach (Rectangle rect in rects) + yield return new Tuple(rect, detectionConfidences[index++]); + } + + #region Helpers + + private static void SplitRectDetections(RectDetection[] rectDetections, + List rectangles, + List detectionConfidences, + List weightIndices) + { + rectangles.Clear(); + detectionConfidences.Clear(); + weightIndices.Clear(); + + foreach (RectDetection? rectDetection in rectDetections) + { + rectangles.Add(rectDetection.Rect); + detectionConfidences.Add(rectDetection.DetectionConfidence); + weightIndices.Add(rectDetection.WeightIndex); + } + } + + #endregion + + #endregion + +} \ No newline at end of file diff --git a/FaceRecognitionDotNet/Extensions/FaceDetector.cs b/FaceRecognitionDotNet/Extensions/FaceDetector.cs new file mode 100644 index 0000000..d04b035 --- /dev/null +++ b/FaceRecognitionDotNet/Extensions/FaceDetector.cs @@ -0,0 +1,26 @@ +using DlibDotNet; +using View_by_Distance.Shared.Models; + +namespace View_by_Distance.FaceRecognitionDotNet.Extensions; + +/// +/// An abstract base class that provides functionality to detect face locations from image. +/// +public abstract class FaceDetector : DisposableObject +{ + + #region Methods + + internal IEnumerable Detect(Image image, int numberOfTimesToUpsample) => RawDetect(image.Matrix, numberOfTimesToUpsample); + + /// + /// Returns an enumerable collection of face location correspond to all faces in specified image. + /// + /// The matrix contains a face. + /// The number of times to up-sample the image when finding faces. + /// An enumerable collection of face location correspond to all faces. + protected abstract IEnumerable RawDetect(MatrixBase matrix, int numberOfTimesToUpsample); + + #endregion + +} \ No newline at end of file diff --git a/FaceRecognitionDotNet/Extensions/FaceLandmarkDetector.cs b/FaceRecognitionDotNet/Extensions/FaceLandmarkDetector.cs new file mode 100644 index 0000000..a222887 --- /dev/null +++ b/FaceRecognitionDotNet/Extensions/FaceLandmarkDetector.cs @@ -0,0 +1,36 @@ +using DlibDotNet; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Stateless; + +namespace View_by_Distance.FaceRecognitionDotNet.Extensions; + +/// +/// An abstract base class that provides functionality to detect face parts locations from face image. +/// +public abstract class FaceLandmarkDetector : DisposableObject +{ + + #region Methods + + internal FullObjectDetection Detect(Image image, Location location) => RawDetect(image.Matrix, location); + + internal IEnumerable>> GetLandmarks(IEnumerable landmarkTuples) => RawGetLandmarks(landmarkTuples); + + /// + /// Returns an object contains information of face parts corresponds to specified location in specified image. + /// + /// The matrix contains a face. + /// The location rectangle for a face. + /// An object contains information of face parts. + protected abstract FullObjectDetection RawDetect(MatrixBase matrix, Location location); + + /// + /// Returns an enumerable collection of dictionary of face parts locations (eyes, nose, etc). + /// + /// The enumerable collection of face parts location. + /// An enumerable collection of dictionary of face parts locations (eyes, nose, etc). + protected abstract IEnumerable>> RawGetLandmarks(IEnumerable landmarkTuples); + + #endregion + +} \ No newline at end of file diff --git a/FaceRecognitionDotNet/FaceEncoding.cs b/FaceRecognitionDotNet/FaceEncoding.cs new file mode 100644 index 0000000..fad24b3 --- /dev/null +++ b/FaceRecognitionDotNet/FaceEncoding.cs @@ -0,0 +1,105 @@ +using DlibDotNet; +using System.Runtime.Serialization; + +namespace View_by_Distance.FaceRecognitionDotNet; + +/// +/// Represents a feature data of face. This class cannot be inherited. +/// +[Serializable] +public sealed class FaceEncoding : DisposableObject, ISerializable +{ + + #region Fields + + [NonSerialized] + private readonly Matrix _Encoding; + + #endregion + + #region Constructors + + internal FaceEncoding(Matrix encoding) => _Encoding = encoding; + + private FaceEncoding(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new NullReferenceException(nameof(info)); + + double[]? array = info.GetValue(nameof(_Encoding), typeof(double[])) as double[]; + int? row = (int?)info.GetValue(nameof(_Encoding.Rows), typeof(int)); + int? column = (int?)info.GetValue(nameof(_Encoding.Columns), typeof(int)); + if (row is null) + throw new NullReferenceException(nameof(row)); + if (column is null) + throw new NullReferenceException(nameof(column)); + _Encoding = new Matrix(array, row.Value, column.Value); + } + + #endregion + + #region Properties + + internal Matrix Encoding => _Encoding; + + /// + /// Gets the size of feature data. + /// + /// This object is disposed. + public int Size + { + get + { + ThrowIfDisposed(); + return _Encoding.Size; + } + } + + #endregion + + #region Methods + + /// + /// Gets a feature data of face as raw format. + /// + /// A array that represents a feature data. + /// class supports serialization. This method is for interoperability between FaceRecognitionotNet and dlib. + /// This object is disposed. + public double[] GetRawEncoding() + { + ThrowIfDisposed(); + return _Encoding.ToArray(); + } + + #region Overrides + + /// + /// Releases all unmanaged resources. + /// + protected override void DisposeUnmanaged() + { + base.DisposeUnmanaged(); + _Encoding?.Dispose(); + } + + #endregion + + #endregion + + #region ISerializable Members + + /// + /// Populates a with the data needed to serialize the target object. + /// + /// The to populate with data. + /// The destination (see ) for this serialization. + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(nameof(_Encoding), _Encoding.ToArray()); + info.AddValue(nameof(_Encoding.Rows), _Encoding.Rows); + info.AddValue(nameof(_Encoding.Columns), _Encoding.Columns); + } + + #endregion + +} \ No newline at end of file diff --git a/FaceRecognitionDotNet/FaceRecognition.cs b/FaceRecognitionDotNet/FaceRecognition.cs new file mode 100644 index 0000000..fd99e78 --- /dev/null +++ b/FaceRecognitionDotNet/FaceRecognition.cs @@ -0,0 +1,497 @@ +using DlibDotNet; +using DlibDotNet.Dnn; +using System.Collections.ObjectModel; +using System.Drawing; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; +using View_by_Distance.FaceRecognitionDotNet.Dlib.Python; +using View_by_Distance.FaceRecognitionDotNet.Extensions; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Stateless; +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.FaceRecognitionDotNet; + +public class FaceRecognition : DisposableObject +{ + + public FaceDetector? CustomFaceDetector { get; set; } + public FaceLandmarkDetector? CustomFaceLandmarkDetector { get; set; } + + private readonly Model _Model; + private readonly int _NumberOfJitters; + private readonly LossMetric _FaceEncoder; + private readonly LossMmod _CnnFaceDetector; + private readonly int _NumberOfTimesToUpsample; + private readonly PredictorModel _PredictorModel; + private readonly FrontalFaceDetector _FaceDetector; + private readonly ShapePredictor _PosePredictor5Point; + private readonly ShapePredictor _PosePredictor68Point; + + private record Record(Location Location, List FaceEncodings, List> FaceParts); + + public FaceRecognition(int numberOfJitters, int numberOfTimesToUpsample, Model model, ModelParameter modelParameter, PredictorModel predictorModel) + { + if (modelParameter is null) + throw new NullReferenceException(nameof(modelParameter)); + if (modelParameter.PosePredictor5FaceLandmarksModel is null) + throw new NullReferenceException(nameof(modelParameter.PosePredictor5FaceLandmarksModel)); + if (modelParameter.PosePredictor68FaceLandmarksModel is null) + throw new NullReferenceException(nameof(modelParameter.PosePredictor68FaceLandmarksModel)); + if (modelParameter.CnnFaceDetectorModel is null) + throw new NullReferenceException(nameof(modelParameter.CnnFaceDetectorModel)); + if (modelParameter.FaceRecognitionModel is null) + throw new NullReferenceException(nameof(modelParameter.FaceRecognitionModel)); + _Model = model; + _PredictorModel = predictorModel; + _NumberOfJitters = numberOfJitters; + _NumberOfTimesToUpsample = numberOfTimesToUpsample; + _FaceDetector?.Dispose(); + _FaceDetector = DlibDotNet.Dlib.GetFrontalFaceDetector(); + _PosePredictor68Point?.Dispose(); + _PosePredictor68Point = ShapePredictor.Deserialize(modelParameter.PosePredictor68FaceLandmarksModel); + _PosePredictor5Point?.Dispose(); + _PosePredictor5Point = ShapePredictor.Deserialize(modelParameter.PosePredictor5FaceLandmarksModel); + _CnnFaceDetector?.Dispose(); + _CnnFaceDetector = LossMmod.Deserialize(modelParameter.CnnFaceDetectorModel); + _FaceEncoder?.Dispose(); + _FaceEncoder = LossMetric.Deserialize(modelParameter.FaceRecognitionModel); + } + + public static double FaceDistance(FaceEncoding faceEncoding, FaceEncoding faceToCompare) + { + if (faceEncoding is null) + throw new NullReferenceException(nameof(faceEncoding)); + if (faceToCompare is null) + throw new NullReferenceException(nameof(faceToCompare)); + faceEncoding.ThrowIfDisposed(); + faceToCompare.ThrowIfDisposed(); + if (faceEncoding.Encoding.Size == 0) + return 0; + using Matrix? diff = faceEncoding.Encoding - faceToCompare.Encoding; + return DlibDotNet.Dlib.Length(diff); + } + + private static FacePoint[] Join(IEnumerable facePoints1, IEnumerable facePoints2) + { + List results = [.. facePoints1, .. facePoints2]; + return results.ToArray(); + } + + private List GetFaceParts(FullObjectDetection fullObjectDetection) + { + List results = []; + FacePoint[] facePoints = Enumerable.Range(0, (int)fullObjectDetection.Parts) + .Select(index => new FacePoint(index, fullObjectDetection.GetPart((uint)index).X, fullObjectDetection.GetPart((uint)index).Y)) + .ToArray(); + switch (_PredictorModel) + { + case PredictorModel.Custom: + throw new NotImplementedException(); + case PredictorModel.Large: + if (facePoints.Length == 68) + { + results.Add(new FacePartAndFacePointArray(FacePart.Chin, facePoints.Skip(0).Take(17).ToArray())); + results.Add(new FacePartAndFacePointArray(FacePart.LeftEyebrow, facePoints.Skip(17).Take(5).ToArray())); + results.Add(new FacePartAndFacePointArray(FacePart.RightEyebrow, facePoints.Skip(22).Take(5).ToArray())); + results.Add(new FacePartAndFacePointArray(FacePart.NoseBridge, facePoints.Skip(27).Take(5).ToArray())); + results.Add(new FacePartAndFacePointArray(FacePart.NoseTip, facePoints.Skip(31).Take(5).ToArray())); + results.Add(new FacePartAndFacePointArray(FacePart.LeftEye, facePoints.Skip(36).Take(6).ToArray())); + results.Add(new FacePartAndFacePointArray(FacePart.RightEye, facePoints.Skip(42).Take(6).ToArray())); + results.Add(new FacePartAndFacePointArray(FacePart.TopLip, Join(facePoints.Skip(48).Take(7), facePoints.Skip(60).Take(5)))); + results.Add(new FacePartAndFacePointArray(FacePart.BottomLip, Join(facePoints.Skip(55).Take(5), facePoints.Skip(65).Take(3)))); + } + break; + case PredictorModel.Small: + if (facePoints.Length == 5) + { + results.Add(new FacePartAndFacePointArray(FacePart.RightEye, facePoints.Skip(0).Take(2).ToArray())); + results.Add(new FacePartAndFacePointArray(FacePart.LeftEye, facePoints.Skip(2).Take(2).ToArray())); + results.Add(new FacePartAndFacePointArray(FacePart.NoseTip, facePoints.Skip(4).Take(1).ToArray())); + } + break; + default: + break; + } + return results; + } + + private MModRect[] GetMModRects(Image image) + { + switch (_Model) + { + case Model.Cnn: + return CnnFaceDetectionModelV1.Detect(_CnnFaceDetector, image, _NumberOfTimesToUpsample).ToArray(); + case Model.Hog: + IEnumerable>? locations = SimpleObjectDetector.RunDetectorWithUpscale2(_FaceDetector, image, (uint)_NumberOfTimesToUpsample); + return locations.Select(l => new MModRect { Rect = l.Item1, DetectionConfidence = l.Item2 }).ToArray(); + case Model.Custom: + if (CustomFaceDetector is null) + throw new NotSupportedException("The custom face detector is not ready."); + return CustomFaceDetector.Detect(image, _NumberOfTimesToUpsample).Select(rect => new MModRect + { + Rect = new DlibDotNet.Rectangle(rect.Left, rect.Top, rect.Right, rect.Bottom), + DetectionConfidence = rect.Confidence + }).ToArray(); + default: + throw new Exception(); + } + } + + public List FaceLocations(Image image) + { + if (image is null) + throw new NullReferenceException(nameof(image)); + image.ThrowIfDisposed(); + ThrowIfDisposed(); + List results = []; + System.Drawing.Rectangle rectangle; + IEnumerable mModRects = GetMModRects(image); + foreach (MModRect? mModRect in mModRects) + { + rectangle = new(mModRect.Rect.Left, mModRect.Rect.Top, (int)mModRect.Rect.Width, (int)mModRect.Rect.Height); + Location location = ILocation.TrimBound(mModRect.DetectionConfidence, rectangle, image.Width, image.Height, mModRects.Count()); + mModRect.Dispose(); + results.Add(location); + } + return results; + } + + private List GetFullObjectDetections(Image image, List locations) + { + List results = []; + if (_PredictorModel == PredictorModel.Custom) + { + if (CustomFaceLandmarkDetector is null) + throw new NullReferenceException(nameof(CustomFaceLandmarkDetector)); + foreach (Location location in locations) + { + FullObjectDetection fullObjectDetection = CustomFaceLandmarkDetector.Detect(image, location); + results.Add(fullObjectDetection); + } + } + else + { + ShapePredictor posePredictor = _PredictorModel switch + { + PredictorModel.Large => _PosePredictor68Point, + PredictorModel.Small => _PosePredictor5Point, + PredictorModel.Custom => throw new NotImplementedException(), + _ => throw new Exception() + }; + foreach (Location location in locations) + { + DlibDotNet.Rectangle rectangle = new(location.Left, location.Top, location.Right, location.Bottom); + FullObjectDetection fullObjectDetection = posePredictor.Detect(image.Matrix, rectangle); + results.Add(fullObjectDetection); + } + } + return results; + } + + private List GetLocations(Image image) + { + List results = []; + MModRect[] mModRects = GetMModRects(image); + if (mModRects.Length != 0) + { + Location location; + System.Drawing.Rectangle rectangle; + foreach (MModRect? mModRect in mModRects) + { + rectangle = new(mModRect.Rect.Left, mModRect.Rect.Top, (int)mModRect.Rect.Width, (int)mModRect.Rect.Height); + location = ILocation.TrimBound(mModRect.DetectionConfidence, rectangle, image.Width, image.Height, mModRects.Length); + mModRect.Dispose(); + results.Add(location); + } + } + return results; + } + + public List GetCollection(Image image, List locations, bool includeFaceEncoding, bool includeFaceParts) + { + List results = []; + if (image is null) + throw new NullReferenceException(nameof(image)); + image.ThrowIfDisposed(); + ThrowIfDisposed(); + if (_PredictorModel == PredictorModel.Custom) + throw new NotSupportedException("FaceRecognition.PredictorModel.Custom is not supported."); + if (locations.Count == 0) + locations.AddRange(GetLocations(image)); + List fullObjectDetections = GetFullObjectDetections(image, locations); + if (fullObjectDetections.Count != locations.Count) + throw new Exception(); + Record record; + List records = []; + foreach (Location location in locations) + { + record = new(location, [], []); + records.Add(record); + } + if (locations.Count != records.Count) + throw new Exception(); + if (!includeFaceEncoding) + { + for (int i = 0; i < records.Count; i++) + records[i].FaceEncodings.Add(null); + } + else + { + Matrix doubles; + FaceEncoding faceEncoding; + for (int i = 0; i < records.Count; i++) + { + doubles = FaceRecognitionModelV1.ComputeFaceDescriptor(_FaceEncoder, image, fullObjectDetections[i], _NumberOfJitters); + faceEncoding = new(doubles); + records[i].FaceEncodings.Add(faceEncoding); + } + } + if (!includeFaceParts) + { + for (int i = 0; i < records.Count; i++) + records[i].FaceParts.Add([]); + } + else + { + List faceParts; + for (int i = 0; i < records.Count; i++) + { + faceParts = GetFaceParts(fullObjectDetections[i]); + records[i].FaceParts.Add(faceParts); + } + } + foreach (FullObjectDetection fullObjectDetection in fullObjectDetections) + fullObjectDetection.Dispose(); + const int indexZero = 0; + FaceRecognitionGroup faceRecognitionGroupB; + Dictionary keyValuePairs; + foreach (Record r in records) + { + if (r.FaceEncodings.Count != 1 || r.FaceParts.Count != 1) + continue; + if (r.FaceParts[indexZero].Count == 0) + faceRecognitionGroupB = new(r.Location, r.FaceEncodings[indexZero], null); + else + { + keyValuePairs = []; + foreach (FacePartAndFacePointArray facePartAndFacePointArray in r.FaceParts[indexZero]) + keyValuePairs.Add(facePartAndFacePointArray.FacePart, facePartAndFacePointArray.FacePoints); + faceRecognitionGroupB = new(r.Location, r.FaceEncodings[indexZero], keyValuePairs); + } + results.Add(faceRecognitionGroupB); + } + return results; + } + + public static FaceEncoding LoadFaceEncoding(double[] encoding) + { + if (encoding is null) + throw new NullReferenceException(nameof(encoding)); + if (encoding.Length != 128) + { + string message = $"{nameof(encoding)}.{nameof(encoding.Length)} must be 128."; + throw new ArgumentOutOfRangeException(message); + } +#pragma warning disable + Matrix? matrix = Matrix.CreateTemplateParameterizeMatrix(0, 1); +#pragma warning restore + matrix.SetSize(128); + matrix.Assign(encoding); + return new FaceEncoding(matrix); + } + + public static FaceEncoding LoadBFaceEncoding(double[] encoding) + { + if (encoding is null) + throw new NullReferenceException(nameof(encoding)); + if (encoding.Length != 512) + { + string message = $"{nameof(encoding)}.{nameof(encoding.Length)} must be 512."; + throw new ArgumentOutOfRangeException(message); + } +#pragma warning disable + Matrix? matrix = Matrix.CreateTemplateParameterizeMatrix(0, 1); +#pragma warning restore + matrix.SetSize(512); + matrix.Assign(encoding); + return new FaceEncoding(matrix); + } + + public static Image LoadImageFile(string file, Mode mode = Mode.Rgb) + { + if (!File.Exists(file)) + throw new FileNotFoundException(file); + return mode switch + { + Mode.Rgb => new Image(DlibDotNet.Dlib.LoadImageAsMatrix(file), mode), + Mode.Greyscale => new Image(DlibDotNet.Dlib.LoadImageAsMatrix(file), mode), + _ => throw new NotImplementedException() + }; + } + +#pragma warning disable CA1416 + + public static Image? LoadImage(Bitmap bitmap) + { + Mode mode; + int dstChannel; + int srcChannel; + int width = bitmap.Width; + int height = bitmap.Height; + PixelFormat format = bitmap.PixelFormat; + System.Drawing.Rectangle rect = new(0, 0, width, height); +#pragma warning disable IDE0010 + switch (format) + { + case PixelFormat.Format8bppIndexed: + mode = Mode.Greyscale; + srcChannel = 1; + dstChannel = 1; + break; + case PixelFormat.Format24bppRgb: + mode = Mode.Rgb; + srcChannel = 3; + dstChannel = 3; + break; + case PixelFormat.Format32bppRgb: + case PixelFormat.Format32bppArgb: + mode = Mode.Rgb; + srcChannel = 4; + dstChannel = 3; + break; + default: + throw new ArgumentOutOfRangeException($"{nameof(bitmap)}", $"The specified {nameof(PixelFormat)} is not supported."); + } +#pragma warning restore IDE0010 + BitmapData? data = null; + try + { + data = bitmap.LockBits(rect, ImageLockMode.ReadOnly, format); + unsafe + { + byte[]? array = new byte[width * height * dstChannel]; + fixed (byte* pArray = &array[0]) + { + byte* dst = pArray; + + switch (srcChannel) + { + case 1: + { + IntPtr src = data.Scan0; + int stride = data.Stride; + + for (int h = 0; h < height; h++) + Marshal.Copy(IntPtr.Add(src, h * stride), array, h * width, width * dstChannel); + } + break; + case 3: + case 4: + { + byte* src = (byte*)data.Scan0; + int stride = data.Stride; + for (int h = 0; h < height; h++) + { + int srcOffset = h * stride; + int dstOffset = h * width * dstChannel; + for (int w = 0; w < width; w++) + { + // BGR order to RGB order + dst[dstOffset + (w * dstChannel) + 0] = src[srcOffset + (w * srcChannel) + 2]; + dst[dstOffset + (w * dstChannel) + 1] = src[srcOffset + (w * srcChannel) + 1]; + dst[dstOffset + (w * dstChannel) + 2] = src[srcOffset + (w * srcChannel) + 0]; + } + } + } + break; + default: + break; + } + IntPtr ptr = (IntPtr)pArray; + switch (mode) + { + case Mode.Rgb: + return new Image(new Matrix(ptr, height, width, width * 3), Mode.Rgb); + case Mode.Greyscale: + return new Image(new Matrix(ptr, height, width, width), Mode.Greyscale); + default: + break; + } + } + } + } + finally + { + if (data != null) + bitmap.UnlockBits(data); + } + return null; + } + + public static ReadOnlyCollection GetLocationContainers(int permyriad, ReadOnlyCollection locationContainers, LocationContainer locationContainer) + { + List results = []; + int lengthPermyriad; + if (locationContainers.Count != 0) + { + double length; + LocationContainer result; + if (locationContainer.Encoding is not FaceEncoding faceEncodingToCompare) + throw new NullReferenceException(nameof(locationContainer)); + faceEncodingToCompare.ThrowIfDisposed(); + foreach (LocationContainer l in locationContainers) + { +#pragma warning disable CA1513 + if (l.Encoding is not FaceEncoding faceEncoding || faceEncoding.IsDisposed) + throw new ObjectDisposedException($"{nameof(l)} contains disposed object."); +#pragma warning restore CA1513 + using (Matrix diff = faceEncoding.Encoding - faceEncodingToCompare.Encoding) + length = DlibDotNet.Dlib.Length(diff); + lengthPermyriad = (int)(length * permyriad); + result = LocationContainer.Get(locationContainer, l, lengthPermyriad, keepExifDirectory: false, keepEncoding: false); + results.Add(result); + } + } + LocationContainer[] array = results.OrderBy(l => l.LengthPermyriad).ToArray(); + return array.AsReadOnly(); + } + + public static List FaceDistances(ReadOnlyCollection faceDistances, FaceDistance faceDistanceToCompare) + { + List results = []; + if (faceDistances.Count != 0) + { + double length; + FaceDistance result; + if (faceDistanceToCompare.Encoding is not FaceEncoding faceEncodingToCompare) + throw new NullReferenceException(nameof(faceDistanceToCompare)); + faceEncodingToCompare.ThrowIfDisposed(); + foreach (FaceDistance faceDistance in faceDistances) + { +#pragma warning disable CA1513 + if (faceDistance.Encoding is not FaceEncoding faceEncoding || faceEncoding.IsDisposed) + throw new ObjectDisposedException($"{nameof(faceDistances)} contains disposed object."); +#pragma warning restore CA1513 + using (Matrix diff = faceEncoding.Encoding - faceEncodingToCompare.Encoding) + length = DlibDotNet.Dlib.Length(diff); + result = new(faceDistance, length); + results.Add(result); + } + } + return results; + } + +#pragma warning restore CA1416 + + protected override void DisposeUnmanaged() + { + base.DisposeUnmanaged(); + _PosePredictor68Point?.Dispose(); + _PosePredictor5Point?.Dispose(); + _CnnFaceDetector?.Dispose(); + _FaceEncoder?.Dispose(); + _FaceDetector?.Dispose(); + } + +} \ No newline at end of file diff --git a/FaceRecognitionDotNet/FaceRecognitionGroup.cs b/FaceRecognitionDotNet/FaceRecognitionGroup.cs new file mode 100644 index 0000000..390ec6b --- /dev/null +++ b/FaceRecognitionDotNet/FaceRecognitionGroup.cs @@ -0,0 +1,6 @@ +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Stateless; + +namespace View_by_Distance.FaceRecognitionDotNet; + +public record FaceRecognitionGroup(Location Location, FaceEncoding? FaceEncoding, Dictionary? KeyValuePairs); \ No newline at end of file diff --git a/FaceRecognitionDotNet/FaceRecognitionModels.cs b/FaceRecognitionDotNet/FaceRecognitionModels.cs new file mode 100644 index 0000000..91d1bf8 --- /dev/null +++ b/FaceRecognitionDotNet/FaceRecognitionModels.cs @@ -0,0 +1,22 @@ +namespace View_by_Distance.FaceRecognitionDotNet; + +internal sealed class FaceRecognitionModels +{ + + public static string GetPosePredictorModelLocation() => "shape_predictor_68_face_landmarks.dat"; + + public static string GetPosePredictorFivePointModelLocation() => "shape_predictor_5_face_landmarks.dat"; + + public static string GetFaceRecognitionModelLocation() => "dlib_face_recognition_resnet_model_v1.dat"; + + public static string GetCnnFaceDetectorModelLocation() => "mmod_human_face_detector.dat"; + + public static string GetPosePredictor194PointModelLocation() => "helen-dataset.dat"; + + public static string GetAgeNetworkModelLocation() => "adience-age-network.dat"; + + public static string GetGenderNetworkModelLocation() => "utkface-gender-network.dat"; + + public static string GetEmotionNetworkModelLocation() => "corrective-reannotation-of-fer-ck-kdef-emotion-network_test_best.dat"; + +} \ No newline at end of file diff --git a/FaceRecognitionDotNet/Image.cs b/FaceRecognitionDotNet/Image.cs new file mode 100644 index 0000000..7c61e40 --- /dev/null +++ b/FaceRecognitionDotNet/Image.cs @@ -0,0 +1,129 @@ +using DlibDotNet; +using DlibDotNet.Extensions; +using System.Drawing; +using View_by_Distance.Shared.Models.Stateless; + +namespace View_by_Distance.FaceRecognitionDotNet; + +/// +/// Represents a image data. This class cannot be inherited. +/// +public sealed class Image : DisposableObject +{ + + #region Fields + + #endregion + + #region Constructors + + internal Image(MatrixBase matrix, Mode mode) + { + Matrix = matrix; + Mode = mode; + } + + #endregion + + #region Properties + + /// + /// Gets the height of the image. + /// + /// This object is disposed. + public int Height + { + get + { + ThrowIfDisposed(); + return Matrix.Rows; + } + } + + internal MatrixBase Matrix { get; private set; } + + internal Mode Mode { get; } + + /// + /// Gets the width of the image. + /// + /// This object is disposed. + public int Width + { + get + { + ThrowIfDisposed(); + return Matrix.Columns; + } + } + + #endregion + + #region Methods + + /// + /// Saves this to the specified file. + /// + /// A string that contains the name of the file to which to save this . + /// The for this . + /// is null. + /// This object is disposed. + public void Save(string fileName, ImageFormat format) + { + if (fileName == null) + throw new NullReferenceException(nameof(fileName)); + + ThrowIfDisposed(); + + string? directory = Path.GetDirectoryName(fileName); + if (!Directory.Exists(directory) && !string.IsNullOrWhiteSpace(directory)) + _ = Directory.CreateDirectory(directory); + + switch (format) + { + case ImageFormat.Bmp: + DlibDotNet.Dlib.SaveBmp(Matrix, fileName); + break; + case ImageFormat.Jpeg: + DlibDotNet.Dlib.SaveJpeg(Matrix, fileName); + break; + case ImageFormat.Png: + DlibDotNet.Dlib.SavePng(Matrix, fileName); + break; + default: + break; + } + } + + /// + /// Converts this to a GDI+ . + /// + /// A that represents the converted . + /// This object is disposed. + /// A Greyscale image is not supported. + public Bitmap ToBitmap() + { + ThrowIfDisposed(); + + if (Mode == Mode.Greyscale) + throw new NotSupportedException(); + + return ((Matrix)Matrix).ToBitmap(); + } + + #region Overrides + + /// + /// Releases all unmanaged resources. + /// + protected override void DisposeUnmanaged() + { + base.DisposeUnmanaged(); + Matrix?.Dispose(); + } + + #endregion + + #endregion + +} \ No newline at end of file diff --git a/FaceRecognitionDotNet/ModelParameter.cs b/FaceRecognitionDotNet/ModelParameter.cs new file mode 100644 index 0000000..7443b8e --- /dev/null +++ b/FaceRecognitionDotNet/ModelParameter.cs @@ -0,0 +1,49 @@ +namespace View_by_Distance.FaceRecognitionDotNet; + +/// +/// Describes the model binary datum. This class cannot be inherited. +/// +public sealed class ModelParameter +{ + + #region Properties + + /// + /// Gets or sets the binary data of model for 68 points face landmarks. + /// + public byte[]? PosePredictor68FaceLandmarksModel + { + get; + set; + } + + /// + /// Gets or sets the binary data of model for 5 points face landmarks. + /// + public byte[]? PosePredictor5FaceLandmarksModel + { + get; + set; + } + + /// + /// Gets or sets the binary data of model for face encoding. + /// + public byte[]? FaceRecognitionModel + { + get; + set; + } + + /// + /// Gets or sets the binary data of model for face detector by using CNN. + /// + public byte[]? CnnFaceDetectorModel + { + get; + set; + } + + #endregion + +} \ No newline at end of file diff --git a/FaceRecognitionDotNet/Point.cs b/FaceRecognitionDotNet/Point.cs new file mode 100644 index 0000000..2942b7b --- /dev/null +++ b/FaceRecognitionDotNet/Point.cs @@ -0,0 +1,107 @@ +namespace View_by_Distance.FaceRecognitionDotNet; + +/// +/// Represents an ordered pair of integer x- and y-coordinates that defines a point in a two-dimensional plane. +/// +public readonly struct Point : IEquatable +{ + + #region Constructors + + /// + /// Initializes a new instance of the structure with the specified coordinates. + /// + /// The horizontal position of the point. + /// The vertical position of the point. + public Point(int x, int y) + { + X = x; + Y = y; + } + + internal Point(DlibDotNet.Point point) + { + X = point.X; + Y = point.Y; + } + + #endregion + + #region Properties + + /// + /// Gets the x-coordinate of this . + /// + public int X + { + get; + } + + /// + /// Gets the y-coordinate of this . + /// + public int Y + { + get; + } + + #endregion + + #region Methods + + /// + /// Compares two structures for equality. + /// + /// The point to compare to this instance. + /// true if both structures contain the same and values; otherwise, false. + public bool Equals(Point other) + { + return X == other.X && + Y == other.Y; + } + + #region overrides + + /// + /// Determines whether the specified is a and whether it contains the same coordinates as this . + /// + /// The to compare. + /// true if is a and contains the same and values as this ; otherwise, false. + public override bool Equals(object? obj) => obj is Point point && Equals(point); + + /// + /// Returns the hash code for this . + /// + /// The hash code for this structure. +#pragma warning disable IDE0070 + public override int GetHashCode() +#pragma warning restore IDE0070 + { + int hashCode = 1861411795; + hashCode = hashCode * -1521134295 + X.GetHashCode(); + hashCode = hashCode * -1521134295 + Y.GetHashCode(); + return hashCode; + } + + /// + /// Compares two structures for equality. + /// + /// The first structure to compare. + /// The second structure to compare. + /// true if both the and coordinates of and are equal; otherwise, false. + public static bool operator ==(Point point1, Point point2) => point1.Equals(point2); + + /// + /// Compares two structures for inequality. + /// + /// The first structure to compare. + /// The second structure to compare. + /// true if and have different or coordinates; false if and have the same and coordinates. + + public static bool operator !=(Point point1, Point point2) => !(point1 == point2); + + #endregion + + #endregion + +} \ No newline at end of file diff --git a/Metadata/Models/A_Metadata.cs b/Metadata/Models/A_Metadata.cs index 5475109..744fee6 100644 --- a/Metadata/Models/A_Metadata.cs +++ b/Metadata/Models/A_Metadata.cs @@ -9,41 +9,43 @@ namespace View_by_Distance.Metadata.Models; public class A_Metadata { - private readonly MetadataConfiguration _MetadataConfiguration; + private readonly ResultSettings _ResultSettings; + private readonly MetadataSettings _MetadataSettings; private readonly ReadOnlyDictionary> _FileGroups; - public A_Metadata(MetadataConfiguration metadataConfiguration) + public A_Metadata(ResultSettings resultSettings, MetadataSettings metadataSettings) { - _MetadataConfiguration = metadataConfiguration; - string aResultsFullGroupDirectory = IResult.GetResultsFullGroupDirectory(metadataConfiguration.ResultConfiguration, + _ResultSettings = resultSettings; + _MetadataSettings = metadataSettings; + string aResultsFullGroupDirectory = IResult.GetResultsFullGroupDirectory(resultSettings, nameof(A_Metadata), string.Empty, includeResizeGroup: false, includeModel: false, includePredictorModel: false); - _FileGroups = IPath.GetKeyValuePairs(metadataConfiguration.ResultConfiguration, aResultsFullGroupDirectory, [metadataConfiguration.ResultConfiguration.ResultSingleton]); + _FileGroups = IPath.GetKeyValuePairs(resultSettings, aResultsFullGroupDirectory, [resultSettings.ResultSingleton]); } - private (int, FileInfo) GetFileInfo(ResultConfiguration resultConfiguration, FilePath filePath) + private (int, FileInfo) GetFileInfo(ResultSettings resultSettings, FilePath filePath) { FileInfo result; FileInfo fileInfo = new(filePath.FullName); - (_, int directoryIndex) = IPath.GetDirectoryNameAndIndex(resultConfiguration, filePath); + (_, int directoryIndex) = IPath.GetDirectoryNameAndIndex(resultSettings, filePath); DateTime minimumDateTime = fileInfo.CreationTime < fileInfo.LastWriteTime ? fileInfo.CreationTime : fileInfo.LastWriteTime; - int fileInfoMinimumYear = minimumDateTime.Year < resultConfiguration.EpicYear ? resultConfiguration.EpicYear : minimumDateTime.Year; - result = new(Path.Combine(_FileGroups[fileInfoMinimumYear][_MetadataConfiguration.ResultConfiguration.ResultSingleton][directoryIndex], $"{filePath.NameWithoutExtension}{filePath.ExtensionLowered}.json")); + int fileInfoMinimumYear = minimumDateTime.Year < resultSettings.EpicYear ? resultSettings.EpicYear : minimumDateTime.Year; + result = new(Path.Combine(_FileGroups[fileInfoMinimumYear][_ResultSettings.ResultSingleton][directoryIndex], $"{filePath.NameWithoutExtension}{filePath.ExtensionLowered}.json")); return (fileInfoMinimumYear, result); } - private (int, string) GetJsonFile(ResultConfiguration resultConfiguration, FilePath filePath, ExifDirectory exifDirectory) + private (int, string) GetJsonFile(ResultSettings resultSettings, FilePath filePath, ExifDirectory exifDirectory) { string? result; DateTime? dateTime; dateTime = IDate.GetDateTimeOriginal(exifDirectory); dateTime ??= IDate.GetMinimum(exifDirectory); - (_, int directoryIndex) = IPath.GetDirectoryNameAndIndex(resultConfiguration, filePath); - int exifYear = dateTime.Value.Year < resultConfiguration.EpicYear ? resultConfiguration.EpicYear : dateTime.Value.Year; - result = Path.Combine(_FileGroups[exifYear][_MetadataConfiguration.ResultConfiguration.ResultSingleton][directoryIndex], $"{filePath.NameWithoutExtension}{filePath.ExtensionLowered}.json"); + (_, int directoryIndex) = IPath.GetDirectoryNameAndIndex(resultSettings, filePath); + int exifYear = dateTime.Value.Year < resultSettings.EpicYear ? resultSettings.EpicYear : dateTime.Value.Year; + result = Path.Combine(_FileGroups[exifYear][_ResultSettings.ResultSingleton][directoryIndex], $"{filePath.NameWithoutExtension}{filePath.ExtensionLowered}.json"); return (exifYear, result); } @@ -64,21 +66,21 @@ public class A_Metadata return (json, result); } - public (FileInfo, ExifDirectory) GetMetadataCollection(MetadataConfiguration metadataConfiguration, FilePath filePath, DeterministicHashCode deterministicHashCode) + public (FileInfo, ExifDirectory) GetMetadataCollection(ResultSettings resultSettings, MetadataSettings metadataSettings, FilePath filePath) { ExifDirectory? result; - (int fileInfoMinimumYear, FileInfo fileInfo) = GetFileInfo(metadataConfiguration.ResultConfiguration, filePath); - if (_MetadataConfiguration.ForceMetadataLastWriteTimeToCreationTime && !fileInfo.Exists && File.Exists(Path.ChangeExtension(fileInfo.FullName, ".delete"))) + (int fileInfoMinimumYear, FileInfo fileInfo) = GetFileInfo(resultSettings, filePath); + if (_MetadataSettings.ForceMetadataLastWriteTimeToCreationTime && !fileInfo.Exists && File.Exists(Path.ChangeExtension(fileInfo.FullName, ".delete"))) { File.Move(Path.ChangeExtension(fileInfo.FullName, ".delete"), fileInfo.FullName); fileInfo.Refresh(); } - if (_MetadataConfiguration.ForceMetadataLastWriteTimeToCreationTime && fileInfo.Exists && fileInfo.LastWriteTime != fileInfo.CreationTime) + if (_MetadataSettings.ForceMetadataLastWriteTimeToCreationTime && fileInfo.Exists && fileInfo.LastWriteTime != fileInfo.CreationTime) { File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime); fileInfo.Refresh(); } - if (_MetadataConfiguration.PropertiesChangedForMetadata) + if (_MetadataSettings.PropertiesChangedForMetadata) result = null; else if (!fileInfo.Exists) result = null; @@ -101,8 +103,9 @@ public class A_Metadata if (result is null) { string json; - result = Exif.GetExifDirectory(filePath, deterministicHashCode); - (int exifYear, string jsonFile) = GetJsonFile(metadataConfiguration.ResultConfiguration, filePath, result); + const PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName = null; + result = Exif.GetExifDirectory(filePath, personKeyFormattedAndKeyTicksAndDisplayDirectoryName); + (int exifYear, string jsonFile) = GetJsonFile(_ResultSettings, filePath, result); if (exifYear == fileInfoMinimumYear) json = JsonSerializer.Serialize(result, ExifDirectorySourceGenerationContext.Default.ExifDirectory); else @@ -122,7 +125,7 @@ public class A_Metadata } } } - if (IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null) && _MetadataConfiguration.ForceMetadataLastWriteTimeToCreationTime) + if (IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null) && _MetadataSettings.ForceMetadataLastWriteTimeToCreationTime) { File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime); fileInfo.Refresh(); diff --git a/Metadata/Models/Stateless/Base.cs b/Metadata/Models/Stateless/Base.cs index 23e79f5..ed7dcbb 100644 --- a/Metadata/Models/Stateless/Base.cs +++ b/Metadata/Models/Stateless/Base.cs @@ -62,7 +62,7 @@ internal static class Base results.Add(value); } } - return new(results); + return results.AsReadOnly(); } } \ No newline at end of file diff --git a/Metadata/Models/Stateless/Exif.cs b/Metadata/Models/Stateless/Exif.cs index 5555023..1ede00f 100644 --- a/Metadata/Models/Stateless/Exif.cs +++ b/Metadata/Models/Stateless/Exif.cs @@ -28,22 +28,24 @@ internal abstract class Exif private static Shared.Models.AviDirectory[] GetAviDirectories(IReadOnlyList directories) { List results = []; + Shared.Models.AviDirectory aviDirectory; IEnumerable aviDirectories = directories.OfType(); - foreach (MetadataExtractor.Formats.Avi.AviDirectory aviDirectory in aviDirectories) + foreach (MetadataExtractor.Formats.Avi.AviDirectory a in aviDirectories) { - if (aviDirectory.Tags.Count == 0) + if (a.Tags.Count == 0) continue; DateTime? dateTimeOriginal; - string? duration = aviDirectory.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagDuration); - string? height = aviDirectory.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagHeight); - string? width = aviDirectory.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagWidth); - if (aviDirectory.TryGetDateTime(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal, out DateTime checkDateTime)) + string? duration = a.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagDuration); + string? height = a.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagHeight); + string? width = a.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagWidth); + if (a.TryGetDateTime(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal, out DateTime checkDateTime)) dateTimeOriginal = checkDateTime; else - dateTimeOriginal = GetDateTime(aviDirectory.GetString(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal)); + dateTimeOriginal = GetDateTime(a.GetString(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal)); if (dateTimeOriginal is null && duration is null && height is null && width is null) continue; - results.Add(new(dateTimeOriginal, duration, height, width)); + aviDirectory = new(dateTimeOriginal, duration, height, width); + results.Add(aviDirectory); } return results.ToArray(); } @@ -51,70 +53,71 @@ internal abstract class Exif private static Shared.Models.ExifDirectoryBase[] GetExifBaseDirectories(IReadOnlyList directories) { List results = []; + Shared.Models.ExifDirectoryBase exifDirectoryBase; IEnumerable exifBaseDirectories = directories.OfType(); - foreach (ExifDirectoryBase exifDirectoryBase in exifBaseDirectories) + foreach (ExifDirectoryBase e in exifBaseDirectories) { - if (exifDirectoryBase.Tags.Count == 0) + if (e.Tags.Count == 0) continue; DateTime? dateTime; DateTime checkDateTime; DateTime? dateTimeOriginal; DateTime? dateTimeDigitized; - string? aperture = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagAperture); - string? applicationNotes = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagApplicationNotes); - string? artist = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagArtist); - string? bitsPerSample = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagBitsPerSample); - string? bodySerialNumber = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagBodySerialNumber); - string? cameraOwnerName = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCameraOwnerName); - string? compressedAverageBitsPerPixel = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCompressedAverageBitsPerPixel); - string? compression = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCompression); - string? copyright = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCopyright); - string? documentName = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagDocumentName); - string? exifVersion = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagExifVersion); - string? exposureTime = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagExposureTime); - string? fileSource = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagFileSource); - string? imageDescription = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageDescription); - string? imageHeight = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageHeight); - string? imageNumber = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageNumber); - string? imageUniqueId = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageUniqueId); - string? imageWidth = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageWidth); - string? isoSpeed = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagIsoSpeed); - string? lensMake = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagLensMake); - string? lensModel = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagLensModel); - string? lensSerialNumber = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagLensSerialNumber); - string? make = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagMake); - string? makerNote = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagMakernote); - string? model = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagModel); - string? orientation = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagOrientation); - int? orientationValue = orientation is null ? null : exifDirectoryBase.GetInt32(ExifDirectoryBase.TagOrientation); - string? rating = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagRating); - string? ratingPercent = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagRatingPercent); - string? securityClassification = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagSecurityClassification); - string? shutterSpeed = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagShutterSpeed); - string? software = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagSoftware); - string? timeZone = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagTimeZone); - string? timeZoneDigitized = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagTimeZoneDigitized); - string? timeZoneOriginal = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagTimeZoneOriginal); - string? userComment = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagUserComment); - string? winAuthor = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinAuthor); - string? winComment = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinComment); - string? winKeywords = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinKeywords); - string? winSubject = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinSubject); - string? winTitle = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinTitle); - string? xResolution = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagXResolution); - string? yResolution = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagYResolution); - if (exifDirectoryBase.TryGetDateTime(ExifDirectoryBase.TagDateTime, out checkDateTime)) + string? aperture = e.GetDescription(ExifDirectoryBase.TagAperture); + string? applicationNotes = e.GetDescription(ExifDirectoryBase.TagApplicationNotes); + string? artist = e.GetDescription(ExifDirectoryBase.TagArtist); + string? bitsPerSample = e.GetDescription(ExifDirectoryBase.TagBitsPerSample); + string? bodySerialNumber = e.GetDescription(ExifDirectoryBase.TagBodySerialNumber); + string? cameraOwnerName = e.GetDescription(ExifDirectoryBase.TagCameraOwnerName); + string? compressedAverageBitsPerPixel = e.GetDescription(ExifDirectoryBase.TagCompressedAverageBitsPerPixel); + string? compression = e.GetDescription(ExifDirectoryBase.TagCompression); + string? copyright = e.GetDescription(ExifDirectoryBase.TagCopyright); + string? documentName = e.GetDescription(ExifDirectoryBase.TagDocumentName); + string? exifVersion = e.GetDescription(ExifDirectoryBase.TagExifVersion); + string? exposureTime = e.GetDescription(ExifDirectoryBase.TagExposureTime); + string? fileSource = e.GetDescription(ExifDirectoryBase.TagFileSource); + string? imageDescription = e.GetDescription(ExifDirectoryBase.TagImageDescription); + string? imageHeight = e.GetDescription(ExifDirectoryBase.TagImageHeight); + string? imageNumber = e.GetDescription(ExifDirectoryBase.TagImageNumber); + string? imageUniqueId = e.GetDescription(ExifDirectoryBase.TagImageUniqueId); + string? imageWidth = e.GetDescription(ExifDirectoryBase.TagImageWidth); + string? isoSpeed = e.GetDescription(ExifDirectoryBase.TagIsoSpeed); + string? lensMake = e.GetDescription(ExifDirectoryBase.TagLensMake); + string? lensModel = e.GetDescription(ExifDirectoryBase.TagLensModel); + string? lensSerialNumber = e.GetDescription(ExifDirectoryBase.TagLensSerialNumber); + string? make = e.GetDescription(ExifDirectoryBase.TagMake); + string? makerNote = e.GetDescription(ExifDirectoryBase.TagMakernote); + string? model = e.GetDescription(ExifDirectoryBase.TagModel); + string? orientation = e.GetDescription(ExifDirectoryBase.TagOrientation); + int? orientationValue = orientation is null ? null : e.GetInt32(ExifDirectoryBase.TagOrientation); + string? rating = e.GetDescription(ExifDirectoryBase.TagRating); + string? ratingPercent = e.GetDescription(ExifDirectoryBase.TagRatingPercent); + string? securityClassification = e.GetDescription(ExifDirectoryBase.TagSecurityClassification); + string? shutterSpeed = e.GetDescription(ExifDirectoryBase.TagShutterSpeed); + string? software = e.GetDescription(ExifDirectoryBase.TagSoftware); + string? timeZone = e.GetDescription(ExifDirectoryBase.TagTimeZone); + string? timeZoneDigitized = e.GetDescription(ExifDirectoryBase.TagTimeZoneDigitized); + string? timeZoneOriginal = e.GetDescription(ExifDirectoryBase.TagTimeZoneOriginal); + string? userComment = e.GetDescription(ExifDirectoryBase.TagUserComment); + string? winAuthor = e.GetDescription(ExifDirectoryBase.TagWinAuthor); + string? winComment = e.GetDescription(ExifDirectoryBase.TagWinComment); + string? winKeywords = e.GetDescription(ExifDirectoryBase.TagWinKeywords); + string? winSubject = e.GetDescription(ExifDirectoryBase.TagWinSubject); + string? winTitle = e.GetDescription(ExifDirectoryBase.TagWinTitle); + string? xResolution = e.GetDescription(ExifDirectoryBase.TagXResolution); + string? yResolution = e.GetDescription(ExifDirectoryBase.TagYResolution); + if (e.TryGetDateTime(ExifDirectoryBase.TagDateTime, out checkDateTime)) dateTime = checkDateTime; else - dateTime = GetDateTime(exifDirectoryBase.GetString(ExifDirectoryBase.TagDateTime)); - if (exifDirectoryBase.TryGetDateTime(ExifDirectoryBase.TagDateTimeOriginal, out checkDateTime)) + dateTime = GetDateTime(e.GetString(ExifDirectoryBase.TagDateTime)); + if (e.TryGetDateTime(ExifDirectoryBase.TagDateTimeOriginal, out checkDateTime)) dateTimeOriginal = checkDateTime; else - dateTimeOriginal = GetDateTime(exifDirectoryBase.GetString(ExifDirectoryBase.TagDateTimeOriginal)); - if (exifDirectoryBase.TryGetDateTime(ExifDirectoryBase.TagDateTimeDigitized, out checkDateTime)) + dateTimeOriginal = GetDateTime(e.GetString(ExifDirectoryBase.TagDateTimeOriginal)); + if (e.TryGetDateTime(ExifDirectoryBase.TagDateTimeDigitized, out checkDateTime)) dateTimeDigitized = checkDateTime; else - dateTimeDigitized = GetDateTime(exifDirectoryBase.GetString(ExifDirectoryBase.TagDateTimeDigitized)); + dateTimeDigitized = GetDateTime(e.GetString(ExifDirectoryBase.TagDateTimeDigitized)); if (userComment is not null && userComment.Length > 255) userComment = "..."; if (aperture is null @@ -164,52 +167,53 @@ internal abstract class Exif && xResolution is not null && yResolution is null) continue; - results.Add(new(aperture, - applicationNotes, - artist, - bitsPerSample, - bodySerialNumber, - cameraOwnerName, - compressedAverageBitsPerPixel, - compression, - copyright, - dateTime, - dateTimeDigitized, - dateTimeOriginal, - documentName, - exifVersion, - exposureTime, - fileSource, - imageDescription, - imageHeight, - imageNumber, - imageUniqueId, - imageWidth, - isoSpeed, - lensMake, - lensModel, - lensSerialNumber, - make, - makerNote, - model, - orientation, - orientationValue, - rating, - ratingPercent, - securityClassification, - shutterSpeed, - software, - timeZone, - timeZoneDigitized, - timeZoneOriginal, - userComment, - winAuthor, - winComment, - winKeywords, - winSubject, - winTitle, - xResolution, - yResolution)); + exifDirectoryBase = new(aperture, + applicationNotes, + artist, + bitsPerSample, + bodySerialNumber, + cameraOwnerName, + compressedAverageBitsPerPixel, + compression, + copyright, + dateTime, + dateTimeDigitized, + dateTimeOriginal, + documentName, + exifVersion, + exposureTime, + fileSource, + imageDescription, + imageHeight, + imageNumber, + imageUniqueId, + imageWidth, + isoSpeed, + lensMake, + lensModel, + lensSerialNumber, + make, + makerNote, + model, + orientation, + orientationValue, + rating, + ratingPercent, + securityClassification, + shutterSpeed, + software, + timeZone, + timeZoneDigitized, + timeZoneOriginal, + userComment, + winAuthor, + winComment, + winKeywords, + winSubject, + winTitle, + xResolution, + yResolution); + results.Add(exifDirectoryBase); } return results.ToArray(); } @@ -217,23 +221,25 @@ internal abstract class Exif private static Shared.Models.FileMetadataDirectory[] GetFileMetadataDirectories(string file, IReadOnlyList directories) { List results = []; + Shared.Models.FileMetadataDirectory fileMetadataDirectory; IEnumerable fileMetadataDirectories = directories.OfType(); - foreach (MetadataExtractor.Formats.FileSystem.FileMetadataDirectory fileMetadataDirectory in fileMetadataDirectories) + foreach (MetadataExtractor.Formats.FileSystem.FileMetadataDirectory f in fileMetadataDirectories) { - if (fileMetadataDirectory.Tags.Count == 0) + if (f.Tags.Count == 0) continue; DateTime? fileModifiedDate; - string? fileName = fileMetadataDirectory.GetDescription(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileName); - string? fileSize = fileMetadataDirectory.GetDescription(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileSize); - if (fileMetadataDirectory.TryGetDateTime(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileModifiedDate, out DateTime checkDateTime)) + string? fileName = f.GetDescription(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileName); + string? fileSize = f.GetDescription(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileSize); + if (f.TryGetDateTime(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileModifiedDate, out DateTime checkDateTime)) fileModifiedDate = checkDateTime; else - fileModifiedDate = GetDateTime(fileMetadataDirectory.GetString(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileModifiedDate)); + fileModifiedDate = GetDateTime(f.GetString(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileModifiedDate)); if (fileName is null || !file.EndsWith(fileName)) throw new NotSupportedException($"!{file}.EndsWith({fileName})"); if (fileModifiedDate is null && fileName is null && fileSize is null) continue; - results.Add(new(fileModifiedDate, fileName, fileSize)); + fileMetadataDirectory = new(fileModifiedDate, fileName, fileSize); + results.Add(fileMetadataDirectory); } return results.ToArray(); } @@ -241,16 +247,18 @@ internal abstract class Exif private static Shared.Models.GifHeaderDirectory[] GetGifHeaderDirectories(IReadOnlyList directories) { List results = []; + Shared.Models.GifHeaderDirectory gifHeaderDirectory; IEnumerable gifHeaderDirectories = directories.OfType(); - foreach (MetadataExtractor.Formats.Gif.GifHeaderDirectory gifHeaderDirectory in gifHeaderDirectories) + foreach (MetadataExtractor.Formats.Gif.GifHeaderDirectory g in gifHeaderDirectories) { - if (gifHeaderDirectory.Tags.Count == 0) + if (g.Tags.Count == 0) continue; - string? imageHeight = gifHeaderDirectory.GetDescription(MetadataExtractor.Formats.Gif.GifHeaderDirectory.TagImageHeight); - string? imageWidth = gifHeaderDirectory.GetDescription(MetadataExtractor.Formats.Gif.GifHeaderDirectory.TagImageWidth); + string? imageHeight = g.GetDescription(MetadataExtractor.Formats.Gif.GifHeaderDirectory.TagImageHeight); + string? imageWidth = g.GetDescription(MetadataExtractor.Formats.Gif.GifHeaderDirectory.TagImageWidth); if (imageHeight is null && imageWidth is null) continue; - results.Add(new(imageHeight, imageWidth)); + gifHeaderDirectory = new(imageHeight, imageWidth); + results.Add(gifHeaderDirectory); } return results.ToArray(); } @@ -258,29 +266,31 @@ internal abstract class Exif private static Shared.Models.GpsDirectory[] GetGpsDirectories(IReadOnlyList directories) { List results = []; + Shared.Models.GpsDirectory gpsDirectory; IEnumerable gpsDirectories = directories.OfType(); - foreach (GpsDirectory gpsDirectory in gpsDirectories) + foreach (GpsDirectory g in gpsDirectories) { - if (gpsDirectory.Tags.Count == 0) + if (g.Tags.Count == 0) continue; DateTime? timeStamp; - string? altitude = gpsDirectory.GetDescription(GpsDirectory.TagAltitude); - string? latitude = gpsDirectory.GetDescription(GpsDirectory.TagLatitude); - string? latitudeRef = gpsDirectory.GetDescription(GpsDirectory.TagLatitudeRef); - string? longitude = gpsDirectory.GetDescription(GpsDirectory.TagLongitude); - string? longitudeRef = gpsDirectory.GetDescription(GpsDirectory.TagLongitudeRef); - if (gpsDirectory.TryGetDateTime(GpsDirectory.TagTimeStamp, out DateTime checkDateTime)) + string? altitude = g.GetDescription(GpsDirectory.TagAltitude); + string? latitude = g.GetDescription(GpsDirectory.TagLatitude); + string? latitudeRef = g.GetDescription(GpsDirectory.TagLatitudeRef); + string? longitude = g.GetDescription(GpsDirectory.TagLongitude); + string? longitudeRef = g.GetDescription(GpsDirectory.TagLongitudeRef); + if (g.TryGetDateTime(GpsDirectory.TagTimeStamp, out DateTime checkDateTime)) timeStamp = checkDateTime; else - timeStamp = GetDateTime(gpsDirectory.GetString(GpsDirectory.TagTimeStamp)); + timeStamp = GetDateTime(g.GetString(GpsDirectory.TagTimeStamp)); if (altitude is null && latitude is null && latitudeRef is null && longitude is null && longitudeRef is null && timeStamp is null) continue; - results.Add(new(altitude, - latitude, - latitudeRef, - longitude, - longitudeRef, - timeStamp)); + gpsDirectory = new(altitude, + latitude, + latitudeRef, + longitude, + longitudeRef, + timeStamp); + results.Add(gpsDirectory); } return results.ToArray(); } @@ -288,16 +298,18 @@ internal abstract class Exif private static Shared.Models.JpegDirectory[] GetJpegDirectories(IReadOnlyList directories) { List results = []; + Shared.Models.JpegDirectory jpegDirectory; IEnumerable jpegDirectories = directories.OfType(); - foreach (MetadataExtractor.Formats.Jpeg.JpegDirectory jpegDirectory in jpegDirectories) + foreach (MetadataExtractor.Formats.Jpeg.JpegDirectory j in jpegDirectories) { - if (jpegDirectory.Tags.Count == 0) + if (j.Tags.Count == 0) continue; - string? imageHeight = jpegDirectory.GetDescription(MetadataExtractor.Formats.Jpeg.JpegDirectory.TagImageHeight); - string? imageWidth = jpegDirectory.GetDescription(MetadataExtractor.Formats.Jpeg.JpegDirectory.TagImageWidth); + string? imageHeight = j.GetDescription(MetadataExtractor.Formats.Jpeg.JpegDirectory.TagImageHeight); + string? imageWidth = j.GetDescription(MetadataExtractor.Formats.Jpeg.JpegDirectory.TagImageWidth); if (imageHeight is null && imageWidth is null) continue; - results.Add(new(imageHeight, imageWidth)); + jpegDirectory = new(imageHeight, imageWidth); + results.Add(jpegDirectory); } return results.ToArray(); } @@ -305,6 +317,7 @@ internal abstract class Exif private static Shared.Models.MakernoteDirectory[] GetMakernoteDirectories(IReadOnlyList directories) { List results = []; + Shared.Models.MakernoteDirectory makernoteDirectory; IEnumerable appleMakernoteDirectories = directories.OfType(); foreach (AppleMakernoteDirectory appleMakernoteDirectory in appleMakernoteDirectories) { @@ -315,7 +328,8 @@ internal abstract class Exif string? qualityAndFileFormat = null; if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) continue; - results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat); + results.Add(makernoteDirectory); } IEnumerable canonMakernoteDirectories = directories.OfType(); foreach (CanonMakernoteDirectory canonMakernoteDirectory in canonMakernoteDirectories) @@ -327,7 +341,8 @@ internal abstract class Exif string? qualityAndFileFormat = canonMakernoteDirectory.GetDescription(CanonMakernoteDirectory.CameraSettings.TagQuality); if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) continue; - results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat); + results.Add(makernoteDirectory); } IEnumerable nikonType2MakernoteDirectories = directories.OfType(); foreach (NikonType2MakernoteDirectory nikonType2MakernoteDirectory in nikonType2MakernoteDirectories) @@ -339,7 +354,8 @@ internal abstract class Exif string? qualityAndFileFormat = nikonType2MakernoteDirectory.GetDescription(NikonType2MakernoteDirectory.TagQualityAndFileFormat); if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) continue; - results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat); + results.Add(makernoteDirectory); } IEnumerable olympusMakernoteDirectories = directories.OfType(); foreach (OlympusMakernoteDirectory olympusMakernoteDirectory in olympusMakernoteDirectories) @@ -351,7 +367,8 @@ internal abstract class Exif string? qualityAndFileFormat = olympusMakernoteDirectory.GetDescription(OlympusMakernoteDirectory.TagJpegQuality); if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) continue; - results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat); + results.Add(makernoteDirectory); } IEnumerable panasonicMakernoteDirectories = directories.OfType(); foreach (PanasonicMakernoteDirectory panasonicMakernoteDirectory in panasonicMakernoteDirectories) @@ -363,7 +380,8 @@ internal abstract class Exif string? qualityAndFileFormat = panasonicMakernoteDirectory.GetDescription(PanasonicMakernoteDirectory.TagQualityMode); if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) continue; - results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat); + results.Add(makernoteDirectory); } IEnumerable samsungType2MakernoteDirectories = directories.OfType(); foreach (SamsungType2MakernoteDirectory samsungType2MakernoteDirectory in samsungType2MakernoteDirectories) @@ -375,7 +393,8 @@ internal abstract class Exif string? qualityAndFileFormat = null; if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) continue; - results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat); + results.Add(makernoteDirectory); } IEnumerable sonyType6MakernoteDirectories = directories.OfType(); foreach (SonyType6MakernoteDirectory sonyType6MakernoteDirectory in sonyType6MakernoteDirectories) @@ -387,7 +406,8 @@ internal abstract class Exif string? qualityAndFileFormat = null; if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null) continue; - results.Add(new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat)); + makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat); + results.Add(makernoteDirectory); } return results.ToArray(); } @@ -395,16 +415,18 @@ internal abstract class Exif private static Shared.Models.PhotoshopDirectory[] GetPhotoshopDirectories(IReadOnlyList directories) { List results = []; + Shared.Models.PhotoshopDirectory photoshopDirectory; IEnumerable photoshopDirectories = directories.OfType(); - foreach (MetadataExtractor.Formats.Photoshop.PhotoshopDirectory photoshopDirectory in photoshopDirectories) + foreach (MetadataExtractor.Formats.Photoshop.PhotoshopDirectory p in photoshopDirectories) { - if (photoshopDirectory.Tags.Count == 0) + if (p.Tags.Count == 0) continue; - string? jpegQuality = photoshopDirectory.GetDescription(MetadataExtractor.Formats.Photoshop.PhotoshopDirectory.TagJpegQuality); - string? url = photoshopDirectory.GetDescription(MetadataExtractor.Formats.Photoshop.PhotoshopDirectory.TagUrl); + string? jpegQuality = p.GetDescription(MetadataExtractor.Formats.Photoshop.PhotoshopDirectory.TagJpegQuality); + string? url = p.GetDescription(MetadataExtractor.Formats.Photoshop.PhotoshopDirectory.TagUrl); if (jpegQuality is null && url is null) continue; - results.Add(new(jpegQuality, url)); + photoshopDirectory = new(jpegQuality, url); + results.Add(photoshopDirectory); } return results.ToArray(); } @@ -412,17 +434,19 @@ internal abstract class Exif private static Shared.Models.PngDirectory[] GetPngDirectories(IReadOnlyList directories) { List results = []; + Shared.Models.PngDirectory pngDirectory; IEnumerable pngDirectories = directories.OfType(); - foreach (MetadataExtractor.Formats.Png.PngDirectory pngDirectory in pngDirectories) + foreach (MetadataExtractor.Formats.Png.PngDirectory p in pngDirectories) { - if (pngDirectory.Tags.Count == 0) + if (p.Tags.Count == 0) continue; - string? imageHeight = pngDirectory.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagImageHeight); - string? imageWidth = pngDirectory.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagImageWidth); - string? textualData = pngDirectory.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagTextualData); + string? imageHeight = p.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagImageHeight); + string? imageWidth = p.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagImageWidth); + string? textualData = p.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagTextualData); if (imageHeight is null && imageWidth is null && textualData is null) continue; - results.Add(new(imageHeight, imageWidth, textualData)); + pngDirectory = new(imageHeight, imageWidth, textualData); + results.Add(pngDirectory); } return results.ToArray(); } @@ -430,19 +454,21 @@ internal abstract class Exif private static Shared.Models.QuickTimeMovieHeaderDirectory[] GetQuickTimeMovieHeaderDirectoryDirectories(IReadOnlyList directories) { List results = []; + Shared.Models.QuickTimeMovieHeaderDirectory quickTimeMovieHeaderDirectory; IEnumerable quickTimeMovieHeaderDirectories = directories.OfType(); - foreach (MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory quickTimeMovieHeaderDirectory in quickTimeMovieHeaderDirectories) + foreach (MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory q in quickTimeMovieHeaderDirectories) { - if (quickTimeMovieHeaderDirectory.Tags.Count == 0) + if (q.Tags.Count == 0) continue; DateTime? created; - if (quickTimeMovieHeaderDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated, out DateTime checkDateTime)) + if (q.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated, out DateTime checkDateTime)) created = checkDateTime; else - created = GetDateTime(quickTimeMovieHeaderDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated)); + created = GetDateTime(q.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated)); if (created is null) continue; - results.Add(new(created)); + quickTimeMovieHeaderDirectory = new(created); + results.Add(quickTimeMovieHeaderDirectory); } return results.ToArray(); } @@ -450,19 +476,21 @@ internal abstract class Exif private static Shared.Models.QuickTimeTrackHeaderDirectory[] GetQuickTimeTrackHeaderDirectoryDirectories(IReadOnlyList directories) { List results = []; + Shared.Models.QuickTimeTrackHeaderDirectory quickTimeTrackHeaderDirectory; IEnumerable quickTimeTrackHeaderDirectories = directories.OfType(); - foreach (MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory quickTimeTrackHeaderDirectory in quickTimeTrackHeaderDirectories) + foreach (MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory q in quickTimeTrackHeaderDirectories) { - if (quickTimeTrackHeaderDirectory.Tags.Count == 0) + if (q.Tags.Count == 0) continue; DateTime? created; - if (quickTimeTrackHeaderDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated, out DateTime checkDateTime)) + if (q.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated, out DateTime checkDateTime)) created = checkDateTime; else - created = GetDateTime(quickTimeTrackHeaderDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated)); + created = GetDateTime(q.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated)); if (created is null) continue; - results.Add(new(created)); + quickTimeTrackHeaderDirectory = new(created); + results.Add(quickTimeTrackHeaderDirectory); } return results.ToArray(); } @@ -470,21 +498,23 @@ internal abstract class Exif private static Shared.Models.WebPDirectory[] GetWebPDirectories(IReadOnlyList directories) { List results = []; + Shared.Models.WebPDirectory webPDirectory; IEnumerable webPDirectories = directories.OfType(); - foreach (MetadataExtractor.Formats.WebP.WebPDirectory webPDirectory in webPDirectories) + foreach (MetadataExtractor.Formats.WebP.WebPDirectory w in webPDirectories) { - if (webPDirectory.Tags.Count == 0) + if (w.Tags.Count == 0) continue; - string? imageHeight = webPDirectory.GetDescription(MetadataExtractor.Formats.WebP.WebPDirectory.TagImageHeight); - string? imageWidth = webPDirectory.GetDescription(MetadataExtractor.Formats.WebP.WebPDirectory.TagImageWidth); + string? imageHeight = w.GetDescription(MetadataExtractor.Formats.WebP.WebPDirectory.TagImageHeight); + string? imageWidth = w.GetDescription(MetadataExtractor.Formats.WebP.WebPDirectory.TagImageWidth); if (imageHeight is null && imageWidth is null) continue; - results.Add(new(imageHeight, imageWidth)); + webPDirectory = new(imageHeight, imageWidth); + results.Add(webPDirectory); } return results.ToArray(); } - private static Shared.Models.ExifDirectory Covert(Shared.Models.FilePath filePath, Shared.Models.DeterministicHashCode deterministicHashCode, System.Drawing.Size? size, IReadOnlyList directories) + private static Shared.Models.ExifDirectory Covert(Shared.Models.FilePath filePath, Shared.Models.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName, System.Drawing.Size? size, IReadOnlyList directories) { Shared.Models.ExifDirectory result; Shared.Models.AviDirectory[] aviDirectories = GetAviDirectories(directories); @@ -500,15 +530,16 @@ internal abstract class Exif Shared.Models.QuickTimeMovieHeaderDirectory[] quickTimeMovieHeaderDirectories = GetQuickTimeMovieHeaderDirectoryDirectories(directories); Shared.Models.QuickTimeTrackHeaderDirectory[] quickTimeTrackHeaderDirectories = GetQuickTimeTrackHeaderDirectoryDirectories(directories); result = new(aviDirectories, + null, exifBaseDirectories, fileMetadataDirectories, + filePath, gifHeaderDirectories, gpsDirectories, size?.Height, - deterministicHashCode.Id ?? filePath.Id, jpegDirectories, MakernoteDirectories, - filePath.Name, + personKeyFormattedAndKeyTicksAndDisplayDirectoryName, photoshopDirectories, pngDirectories, quickTimeMovieHeaderDirectories, @@ -518,7 +549,7 @@ internal abstract class Exif return result; } - internal static Shared.Models.ExifDirectory GetExifDirectory(Shared.Models.FilePath filePath, Shared.Models.DeterministicHashCode deterministicHashCode) + internal static Shared.Models.ExifDirectory GetExifDirectory(Shared.Models.FilePath filePath, Shared.Models.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName) { Shared.Models.ExifDirectory result; System.Drawing.Size? size; @@ -527,7 +558,7 @@ internal abstract class Exif catch (Exception) { size = null; } IReadOnlyList directories = ImageMetadataReader.ReadMetadata(filePath.FullName); - result = Covert(filePath, deterministicHashCode, size, directories); + result = Covert(filePath, personKeyFormattedAndKeyTicksAndDisplayDirectoryName, size, directories); return result; } diff --git a/Metadata/Models/Stateless/Face.cs b/Metadata/Models/Stateless/Face.cs index 30c77ee..8cb08b1 100644 --- a/Metadata/Models/Stateless/Face.cs +++ b/Metadata/Models/Stateless/Face.cs @@ -35,6 +35,17 @@ internal static class Face result = pngDirectory.TextualData[artist.Length..]; break; } + if (result is null) + { + const string author = "Author:"; + foreach (PngDirectory pngDirectory in pngDirectories) + { + if (pngDirectory.TextualData is null || !pngDirectory.TextualData.StartsWith(author)) + continue; + result = pngDirectory.TextualData[author.Length..]; + break; + } + } } return result; } diff --git a/Metadata/Models/Stateless/Get.cs b/Metadata/Models/Stateless/Get.cs index 94d2b42..f83f32e 100644 --- a/Metadata/Models/Stateless/Get.cs +++ b/Metadata/Models/Stateless/Get.cs @@ -28,42 +28,39 @@ internal static class Get } fileHolders.Add(fileHolder); } - return new(results); + return results.AsReadOnly(); } - internal static Action SetExifDirectoryCollection(IRename rename, IRenameConfiguration renameConfiguration, A_Metadata metadata, List distinct, List<(bool, FilePath, FileInfo, ExifDirectory, ReadOnlyCollection)> collection) + internal static Action SetExifDirectoryCollection(IRename rename, ResultSettings resultSettings, MetadataSettings metadataSettings, IRenameSettings renameSettings, A_Metadata metadata, List distinct, List metadataGroups) { return file => { rename.Tick(); FileInfo fileInfo; ExifDirectory exifDirectory; - DeterministicHashCode deterministicHashCode; + MetadataGroup metadataGroup; FileHolder fileHolder = FileHolder.Get(file); bool fastForwardMovingPictureExpertsGroupUsed; FilePath? fastForwardMovingPictureExpertsGroupFilePath; ReadOnlyCollection? fastForwardMovingPictureExpertsGroupFiles; - FilePath filePath = FilePath.Get(renameConfiguration.MetadataConfiguration, fileHolder, index: null); + FilePath filePath = FilePath.Get(resultSettings, metadataSettings, fileHolder, index: null); string key = $"{Path.Combine(fileHolder.DirectoryFullPath ?? throw new NotSupportedException(), fileHolder.NameWithoutExtension)}"; if (distinct.Contains(key)) throw new NotSupportedException("Turn off parallelism when sidecar files are present!"); - if (!renameConfiguration.SkipIdFiles || filePath.Id is null || (!filePath.IsIntelligentIdFormat && filePath.SortOrder is not null)) + if (!renameSettings.SkipIdFiles || filePath.Id is null || (!filePath.IsIntelligentIdFormat && filePath.SortOrder is not null)) { if (filePath.Id is not null) - { fastForwardMovingPictureExpertsGroupFiles = null; - deterministicHashCode = new(null, filePath.Id, null); - } else { - fastForwardMovingPictureExpertsGroupFiles = rename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(renameConfiguration, filePath); - fastForwardMovingPictureExpertsGroupFilePath = fastForwardMovingPictureExpertsGroupFiles.Count == 0 ? null : FilePath.Get(renameConfiguration.MetadataConfiguration, FileHolder.Get(fastForwardMovingPictureExpertsGroupFiles[0]), index: null); - deterministicHashCode = fastForwardMovingPictureExpertsGroupFilePath is null ? rename.GetDeterministicHashCode(filePath) : rename.GetDeterministicHashCode(fastForwardMovingPictureExpertsGroupFilePath); + fastForwardMovingPictureExpertsGroupFiles = rename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(renameSettings, filePath); + fastForwardMovingPictureExpertsGroupFilePath = fastForwardMovingPictureExpertsGroupFiles.Count == 0 ? null : FilePath.Get(resultSettings, metadataSettings, FileHolder.Get(fastForwardMovingPictureExpertsGroupFiles[0]), index: null); } fastForwardMovingPictureExpertsGroupUsed = fastForwardMovingPictureExpertsGroupFiles is not null && fastForwardMovingPictureExpertsGroupFiles.Count > 0; - (fileInfo, exifDirectory) = metadata.GetMetadataCollection(renameConfiguration.MetadataConfiguration, filePath, deterministicHashCode); - lock (collection) - collection.Add(new(fastForwardMovingPictureExpertsGroupUsed, filePath, fileInfo, exifDirectory, new([]))); + (fileInfo, exifDirectory) = metadata.GetMetadataCollection(resultSettings, metadataSettings, filePath); + metadataGroup = new(fastForwardMovingPictureExpertsGroupUsed, filePath, fileInfo, exifDirectory, new([])); + lock (metadataGroups) + metadataGroups.Add(metadataGroup); if (fastForwardMovingPictureExpertsGroupUsed && fastForwardMovingPictureExpertsGroupFiles is not null) { foreach (string fastForwardMovingPictureExpertsGroupFile in fastForwardMovingPictureExpertsGroupFiles) diff --git a/Metadata/Models/Stateless/Methods/IMetadata.cs b/Metadata/Models/Stateless/Methods/IMetadata.cs index 08331d0..d3add4c 100644 --- a/Metadata/Models/Stateless/Methods/IMetadata.cs +++ b/Metadata/Models/Stateless/Methods/IMetadata.cs @@ -17,10 +17,10 @@ public interface IMetadata Meters } - ExifDirectory TestStatic_GetExifDirectory(FilePath filePath, DeterministicHashCode deterministicHashCode) => - GetExifDirectory(filePath, deterministicHashCode); - static ExifDirectory GetExifDirectory(FilePath filePath, DeterministicHashCode deterministicHashCode) => - Exif.GetExifDirectory(filePath, deterministicHashCode); + ExifDirectory TestStatic_GetExifDirectory(FilePath filePath, PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName) => + GetExifDirectory(filePath, personKeyFormattedAndKeyTicksAndDisplayDirectoryName); + static ExifDirectory GetExifDirectory(FilePath filePath, PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName) => + Exif.GetExifDirectory(filePath, personKeyFormattedAndKeyTicksAndDisplayDirectoryName); string? TestStatic_GetMaker(ExifDirectory? exifDirectory) => GetMaker(exifDirectory); @@ -57,10 +57,10 @@ public interface IMetadata static double? GetDistance(double originLatitude, double originLongitude, double destinationLatitude, double destinationLongitude, int decimalPlaces = 1, DistanceUnit distanceUnit = DistanceUnit.Miles) => GPS.GetDistance(originLatitude, originLongitude, destinationLatitude, destinationLongitude, decimalPlaces, distanceUnit); - Action TestStatic_SetExifDirectoryCollection(IRename rename, IRenameConfiguration renameConfiguration, A_Metadata metadata, List distinct, List<(bool, FilePath, FileInfo, ExifDirectory, ReadOnlyCollection)> collection) => - SetExifDirectoryCollection(rename, renameConfiguration, metadata, distinct, collection); - static Action SetExifDirectoryCollection(IRename rename, IRenameConfiguration renameConfiguration, A_Metadata metadata, List distinct, List<(bool, FilePath, FileInfo, ExifDirectory, ReadOnlyCollection)> collection) => - Get.SetExifDirectoryCollection(rename, renameConfiguration, metadata, distinct, collection); + Action TestStatic_SetExifDirectoryCollection(IRename rename, ResultSettings resultSettings, MetadataSettings metadataSettings, IRenameSettings renameSettings, A_Metadata metadata, List distinct, List metadataGroups) => + SetExifDirectoryCollection(rename, resultSettings, metadataSettings, renameSettings, metadata, distinct, metadataGroups); + static Action SetExifDirectoryCollection(IRename rename, ResultSettings resultSettings, MetadataSettings metadataSettings, IRenameSettings renameSettings, A_Metadata metadata, List distinct, List metadataGroups) => + Get.SetExifDirectoryCollection(rename, resultSettings, metadataSettings, renameSettings, metadata, distinct, metadataGroups); ReadOnlyDictionary> TestStatic_GetKeyValuePairs(IEnumerable files) => GetKeyValuePairs(files); diff --git a/People/AA.People.csproj b/People/AA.People.csproj new file mode 100644 index 0000000..24a8e4b --- /dev/null +++ b/People/AA.People.csproj @@ -0,0 +1,44 @@ + + + enable + enable + library + win-x64 + net9.0 + + + Phares.AA.People + false + 9.0.100.0 + Mike Phares + Phares + true + snupkg + + + true + true + true + + + Windows + + + OSX + + + Linux + + + + + + + + + + + + + + \ No newline at end of file diff --git a/People/Models/A2_People.cs b/People/Models/A2_People.cs new file mode 100644 index 0000000..f566987 --- /dev/null +++ b/People/Models/A2_People.cs @@ -0,0 +1,3 @@ +namespace View_by_Distance.Metadata.Models; + +public class A2_People() { } \ No newline at end of file diff --git a/People/Models/Stateless/Methods/IPeople.cs b/People/Models/Stateless/Methods/IPeople.cs new file mode 100644 index 0000000..b86a01e --- /dev/null +++ b/People/Models/Stateless/Methods/IPeople.cs @@ -0,0 +1,40 @@ +using System.Collections.ObjectModel; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; + +namespace View_by_Distance.People.Models.Stateless.Methods; + +public interface IPeople +{ + + ReadOnlyCollection TestStatic_GetPersonKeys(ReadOnlyCollection personContainers) => + GetPersonKeys(personContainers); + static ReadOnlyCollection GetPersonKeys(ReadOnlyCollection personContainers) => + People.GetPersonKeys(personContainers); + + ReadOnlyCollection TestStatic_GetPersonContainers(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, ICompareSettings compareSettings) => + GetPersonContainers(resultSettings, metadataSettings, peopleSettings, compareSettings); + static ReadOnlyCollection GetPersonContainers(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, ICompareSettings compareSettings) => + People.GetPersonContainers(resultSettings, metadataSettings, peopleSettings, compareSettings); + + string? TestStatic_VerifyAge(char numberSign, string personDisplayDirectory, string? minusOne, string personDisplayDirectoryName, int? approximateYears, List personKeyFormattedAndPersonBirthdayCollection) => + VerifyAge(numberSign, personDisplayDirectory, minusOne, personDisplayDirectoryName, approximateYears, personKeyFormattedAndPersonBirthdayCollection); + static string? VerifyAge(char numberSign, string personDisplayDirectory, string? minusOne, string personDisplayDirectoryName, int? approximateYears, List personKeyFormattedAndPersonBirthdayCollection) => + People.VerifyAge(numberSign, personDisplayDirectory, minusOne, personDisplayDirectoryName, approximateYears, personKeyFormattedAndPersonBirthdayCollection); + + static ReadOnlyCollection TestStatic_GetJLinkDirectories(ResultSettings resultSettings, PeopleSettings peopleSettings) => + GetJLinkDirectories(resultSettings, peopleSettings); + static ReadOnlyCollection GetJLinkDirectories(ResultSettings resultSettings, PeopleSettings peopleSettings) => + People.GetJLinkDirectories(resultSettings, peopleSettings); + + static ReadOnlyCollection TestStatic_GetJLinkResolvedDirectories(ResultSettings resultSettings, PeopleSettings peopleSettings) => + GetJLinkResolvedDirectories(resultSettings, peopleSettings); + static ReadOnlyCollection GetJLinkResolvedDirectories(ResultSettings resultSettings, PeopleSettings peopleSettings) => + People.GetJLinkResolvedDirectories(resultSettings, peopleSettings); + + static ReadOnlyCollections TestStatic_GetReadOnlyCollections(ResultSettings resultSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ReadOnlyCollection personContainers, ReadOnlyCollection personKeys) => + GetReadOnlyCollections(resultSettings, peopleSettings, distanceSettings, compareSettings, personContainers, personKeys); + static ReadOnlyCollections GetReadOnlyCollections(ResultSettings resultSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ReadOnlyCollection personContainers, ReadOnlyCollection personKeys) => + ReadOnlyCollectionsLogic.GetReadOnlyCollections(resultSettings, peopleSettings, distanceSettings, compareSettings, personContainers, personKeys); + +} \ No newline at end of file diff --git a/People/Models/Stateless/People.cs b/People/Models/Stateless/People.cs new file mode 100644 index 0000000..b8a0259 --- /dev/null +++ b/People/Models/Stateless/People.cs @@ -0,0 +1,558 @@ +using System.Collections.ObjectModel; +using System.Globalization; +using View_by_Distance.Metadata.Models; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; +using View_by_Distance.Shared.Models.Stateless.Methods; +using WindowsShortcutFactory; + +namespace View_by_Distance.People.Models.Stateless.Methods; + +internal static class People +{ + + private record RecordA(List Changes, List PersonContainers); + private record RecordB(string Directory, string DirectoryName, string DirectoryNameSplitFirst); + + internal static List GetPersonBirthdays(string personBirthdayFormat, string[] personKeyDirectories, string personDisplayDirectoryName) + { + List results = []; + string personKeyFormatted; + PersonBirthday? personBirthday; + PersonKeyFormattedAndPersonBirthday personKeyFormattedAndPersonBirthday; + foreach (string personKeyDirectory in personKeyDirectories) + { + personKeyFormatted = Path.GetFileName(personKeyDirectory); + if (DateTime.TryParseExact(personKeyFormatted, "MM.dd.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime birthday)) + continue; + personBirthday = IPersonBirthday.GetPersonBirthday(personBirthdayFormat, personKeyFormatted); + if (personBirthday is null) + continue; + if (!IPersonBirthday.IsCounterPersonBirthday(personBirthday) && ((!personKeyDirectory.Contains('#') && (personDisplayDirectoryName.Contains('~') || personDisplayDirectoryName.Contains('#'))) || (personKeyDirectory.Contains('#') && !personDisplayDirectoryName.Contains('#')))) + throw new NotSupportedException(); + personKeyFormattedAndPersonBirthday = new(personKeyFormatted, personBirthday); + results.Add(personKeyFormattedAndPersonBirthday); + } + return results; + } + + private static List GetPersonContainersCollections(ResultSettings resultSettings, MetadataSettings metadataSettings, PersonDirectory personDirectory, char numberSign, string personDisplayDirectory, string personDisplayDirectoryName, int? approximateYears, List personKeyFormattedAndPersonBirthdayCollection) + { + List results = []; + long personKey; + string[] files; + FilePath filePath; + const int zero = 0; + string personKeyDirectory; + FileHolder fileHolder; + PersonContainer personContainer; + PersonBirthday[] orderedPersonBirthdays; + List personDisplayDirectoryAllFilePaths = GetFilePaths(resultSettings, metadataSettings, personDisplayDirectory); + foreach (PersonKeyFormattedAndPersonBirthday personKeyFormattedAndPersonBirthday in personKeyFormattedAndPersonBirthdayCollection) + { + orderedPersonBirthdays = (from l in personKeyFormattedAndPersonBirthdayCollection where !l.PersonKeyFormatted.Contains(numberSign) orderby l.PersonBirthday.Value.Ticks descending select l.PersonBirthday).ToArray(); + if (orderedPersonBirthdays.Length == 0) + personKey = personKeyFormattedAndPersonBirthdayCollection[zero].PersonBirthday.Value.Ticks; + else + { + if (personKeyFormattedAndPersonBirthday.PersonKeyFormatted.Contains(numberSign)) + continue; + personKey = orderedPersonBirthdays[zero].Value.Ticks; + } + personKeyDirectory = Path.Combine(personDisplayDirectory, personKeyFormattedAndPersonBirthday.PersonKeyFormatted); + files = Directory.GetFiles(personKeyDirectory, "*", SearchOption.AllDirectories); + if (files.Length == 0) + continue; + foreach (string file in files) + { + if (!file.EndsWith(".rel")) + continue; + fileHolder = FileHolder.Get(file); + filePath = FilePath.Get(resultSettings, metadataSettings, fileHolder, index: null); + personDisplayDirectoryAllFilePaths.Add(filePath); + } + personContainer = new(approximateYears, orderedPersonBirthdays, new(personDisplayDirectoryAllFilePaths), personDisplayDirectoryName, personKey, personDirectory); + results.Add(personContainer); + } + return results; + } + + private static List GetFilePaths(ResultSettings resultSettings, MetadataSettings metadataSettings, string personDisplayDirectory) + { + List results = []; + string[] files; + string checkFile; + FilePath filePath; + string directoryName; + FileHolder fileHolder; + List distinct = []; + string fileNameWithoutExtension; + string personDisplayDirectoryName = Path.GetFileName(personDisplayDirectory); + files = Directory.GetFiles(personDisplayDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string file in files) + { + fileHolder = FileHolder.Get(file); + filePath = FilePath.Get(resultSettings, metadataSettings, fileHolder, index: null); + results.Add(filePath); + } + string[] directories = Directory.GetDirectories(personDisplayDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string directory in directories) + { + directoryName = Path.GetFileName(directory); + files = Directory.GetFiles(directory, "*", SearchOption.TopDirectoryOnly); + foreach (string file in files) + { + fileHolder = FileHolder.Get(file); + filePath = FilePath.Get(resultSettings, metadataSettings, fileHolder, index: null); + if (filePath.ExtensionLowered is not ".json" and not ".pged") + { + results.Add(filePath); + continue; + } + fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); + if (string.IsNullOrEmpty(fileNameWithoutExtension) || string.IsNullOrEmpty(personDisplayDirectoryName)) + continue; + else if (fileNameWithoutExtension.Length == 1 && fileNameWithoutExtension[0] == personDisplayDirectoryName[0]) + { + if (distinct.Contains(file)) + throw new NotSupportedException($"Move / Delete <{file}>"); + distinct.Add(file); + } + else if (fileNameWithoutExtension != directoryName) + { + checkFile = Path.Combine(directory, $"{fileNameWithoutExtension}{filePath.ExtensionLowered}"); + if (!File.Exists(checkFile)) + { + File.Move(file, checkFile); + fileHolder = FileHolder.Get(checkFile); + filePath = FilePath.Get(resultSettings, metadataSettings, fileHolder, index: null); + results.Add(filePath); + } + else + { + checkFile = Path.Combine(directory, $"{fileNameWithoutExtension}.txt"); + if (File.Exists(checkFile)) + File.Delete(checkFile); + File.Move(file, checkFile); + } + continue; + } + results.Add(filePath); + } + } + return results; + } + + private static List GetFilePaths(ResultSettings resultSettings, MetadataSettings metadataSettings, ICompareSettings compareSettings, string personDisplayDirectory) + { + List results; + string checkFile; + FilePath filePath; + FileHolder fileHolder; + int? wholePercentages; + string? checkDirectory; + string[] files = Directory.GetFiles(personDisplayDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string file in files) + { + if (file.EndsWith(".lnk")) + continue; + fileHolder = FileHolder.Get(file); + filePath = FilePath.Get(resultSettings, metadataSettings, fileHolder, index: null); + wholePercentages = IMapping.GetWholePercentages(compareSettings, filePath); + if (filePath.Id is not null && wholePercentages is not null) + continue; + checkDirectory = Path.GetDirectoryName(file); + if (string.IsNullOrEmpty(checkDirectory)) + continue; + checkDirectory = Path.Combine(checkDirectory, "_ Invalid"); + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + checkFile = Path.Combine(checkDirectory, Path.GetFileName(file)); + if (File.Exists(checkFile)) + File.Delete(file); + else + File.Move(file, checkFile); + } + results = GetFilePaths(resultSettings, metadataSettings, personDisplayDirectory); + return results; + } + + private static RecordA GetPersonContainersGroup(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, ICompareSettings compareSettings, PersonDirectory personDirectory, string[] personDisplayDirectories) + { + RecordA result; + string? minusOne; + char numberSign = '#'; + int? approximateYears; + List changes = []; + string[] personKeyDirectories; + PersonContainer personContainer; + List results = []; + string? personDisplayDirectoryName; + List personContainers; + List personDisplayDirectoryAllFilePaths; + char[] personCharacters = peopleSettings.PersonCharacters.ToArray(); + List personKeyFormattedAndPersonBirthdayCollection; + foreach (string personDisplayDirectory in personDisplayDirectories) + { + personDisplayDirectoryName = Path.GetFileName(personDisplayDirectory); + if (string.IsNullOrEmpty(personDisplayDirectoryName)) + continue; + approximateYears = IAge.GetApproximateYears(personCharacters, personDisplayDirectoryName); + personKeyDirectories = Directory.GetDirectories(personDisplayDirectory, "*", SearchOption.TopDirectoryOnly); + personKeyFormattedAndPersonBirthdayCollection = GetPersonBirthdays(peopleSettings.PersonBirthdayFormat, personKeyDirectories, personDisplayDirectoryName); + if (personDisplayDirectoryName.Contains('^')) + { + minusOne = Path.GetDirectoryName(personDisplayDirectory); + if (minusOne is null) + continue; + changes.Add(VerifyAge(numberSign, personDisplayDirectory, minusOne, personDisplayDirectoryName, approximateYears, personKeyFormattedAndPersonBirthdayCollection)); + } + if (changes.Any(l => l is not null)) + continue; + if (personKeyFormattedAndPersonBirthdayCollection.Count > 0) + { + personContainers = GetPersonContainersCollections(resultSettings, metadataSettings, personDirectory, numberSign, personDisplayDirectory, personDisplayDirectoryName, approximateYears, personKeyFormattedAndPersonBirthdayCollection); + results.AddRange(personContainers); + } + else + { + personDisplayDirectoryAllFilePaths = GetFilePaths(resultSettings, metadataSettings, compareSettings, personDisplayDirectory); + personContainer = PersonContainer.Get(approximateYears, new(personDisplayDirectoryAllFilePaths), personDisplayDirectoryName, personDirectory); + results.Add(personContainer); + } + } + result = new(changes, results); + return result; + } + + private static RecordA GetPersonContainersInnerGroups(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, ICompareSettings compareSettings, string groupDirectory, string groupDirectoryName) + { + RecordA result; + string[] segments; + const int zero = 0; + List changes; + List allChanges = []; + string innerGroupDirectoryName; + PersonDirectory personDirectory; + List collection; + const char exclamationPoint = '!'; + string[] personDisplayDirectories; + List results = []; + char @char = groupDirectoryName[zero]; + string[] innerGroupDirectories = Directory.GetDirectories(groupDirectory, "*", SearchOption.TopDirectoryOnly); + if (@char == exclamationPoint) + { + personDirectory = new(@char, "Ignore", 'U', 'U', 'U'); + (changes, collection) = GetPersonContainersGroup(resultSettings, metadataSettings, peopleSettings, compareSettings, personDirectory, innerGroupDirectories); + allChanges.AddRange(changes); + results.AddRange(collection); + } + else + { + foreach (string innerGroupDirectory in innerGroupDirectories) + { + innerGroupDirectoryName = Path.GetFileName(innerGroupDirectory); + segments = innerGroupDirectoryName.Split('-'); + if (segments.Length == 0) + throw new NotSupportedException("Misplaced directory!"); + if (segments.Length != 3) + continue; + if (segments[zero] is not "Alive" and not "Dead" and not "Unknown") + continue; + if (segments[1] is not "Male" and not "Female" and not "Unknown") + continue; + if (segments[2] is not "Yes" and not "No" and not "Unknown") + continue; + personDirectory = new(@char, innerGroupDirectoryName, segments[zero][zero], segments[1][zero], segments[2][zero]); + personDisplayDirectories = Directory.GetDirectories(innerGroupDirectory, "*", SearchOption.TopDirectoryOnly); + (changes, collection) = GetPersonContainersGroup(resultSettings, metadataSettings, peopleSettings, compareSettings, personDirectory, personDisplayDirectories); + allChanges.AddRange(changes); + results.AddRange(collection); + } + } + result = new(allChanges, results); + return result; + } + + private static List GetPersonContainersGroups(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, ICompareSettings compareSettings, string[] groupDirectories) + { + List results; + List changes; + string groupDirectoryName; + List allChanges = []; + List collection; + List personContainers = []; + for (int i = 0; i < peopleSettings.PersonCharacters.Length; i++) + { + foreach (string groupDirectory in groupDirectories) + { + groupDirectoryName = Path.GetFileName(groupDirectory); + if (peopleSettings.PersonCharacters[i] != groupDirectoryName.First()) + continue; + (changes, collection) = GetPersonContainersInnerGroups(resultSettings, metadataSettings, peopleSettings, compareSettings, groupDirectory, groupDirectoryName); + allChanges.AddRange(changes); + personContainers.AddRange(collection); + } + } + if (allChanges.Any(l => l is not null)) + throw new NotImplementedException($"A directory was changed restart to look for more! {string.Join(Environment.NewLine, (from l in allChanges where l is not null select l).ToArray())}"); + results = (from l in personContainers orderby l.Key is not null, l.Key select l).ToList(); + return results; + } + + internal static ReadOnlyCollection GetPersonContainers(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, ICompareSettings compareSettings) + { + List results; + string a2PeopleSingletonDirectoryChar; + string a2PeopleSingletonDirectory = Path.GetFullPath(IResult.GetResultsDateGroupDirectory(resultSettings, nameof(A2_People), resultSettings.ResultSingleton)); + if (!Directory.Exists(a2PeopleSingletonDirectory)) + _ = Directory.CreateDirectory(a2PeopleSingletonDirectory); + foreach (char personCharacter in peopleSettings.PersonCharacters) + { + a2PeopleSingletonDirectoryChar = Path.Combine(a2PeopleSingletonDirectory, personCharacter.ToString()); + if (!Directory.Exists(a2PeopleSingletonDirectoryChar)) + _ = Directory.CreateDirectory(a2PeopleSingletonDirectoryChar); + } + string[] groupDirectories = Directory.GetDirectories(a2PeopleSingletonDirectory, "*", SearchOption.TopDirectoryOnly); + if (groupDirectories.Length == 0) + results = []; + else + results = GetPersonContainersGroups(resultSettings, metadataSettings, peopleSettings, compareSettings, groupDirectories); + return results.AsReadOnly(); + } + + internal static ReadOnlyCollection GetPersonKeys(ReadOnlyCollection personContainers) + { + List results = []; + long personKey; + foreach (PersonContainer personContainer in personContainers) + { + if (personContainer.Key is null || personContainer.Birthdays is null || personContainer.Birthdays.Length == 0) + continue; + foreach (PersonBirthday personBirthday in personContainer.Birthdays) + { + personKey = personBirthday.Value.Ticks; + results.Add(personKey); + } + } + return results.AsReadOnly(); + } + + internal static string? VerifyAge(char numberSign, string personDisplayDirectory, string? minusOne, string personDisplayDirectoryName, int? approximateYears, List personKeyFormattedAndPersonBirthdayCollection) + { + string? result; + if (approximateYears is null) + throw new NotSupportedException(); + if (personKeyFormattedAndPersonBirthdayCollection.Count == 0) + throw new NotSupportedException(); + const int zero = 0; + int? updateApproximateYears; + DateTime dateTime = DateTime.Now; + PersonBirthday[] orderedPersonBirthdays = (from l in personKeyFormattedAndPersonBirthdayCollection where !l.PersonKeyFormatted.Contains(numberSign) orderby l.PersonBirthday.Value.Ticks descending select l.PersonBirthday).ToArray(); + TimeSpan timeSpan = new(orderedPersonBirthdays[zero].Value.Ticks - dateTime.AddYears(-approximateYears.Value).Ticks); + if (timeSpan.TotalDays < -366) + updateApproximateYears = approximateYears.Value + 1; + else if (timeSpan.TotalDays > 1) + updateApproximateYears = approximateYears.Value - 1; + else + updateApproximateYears = null; + if (minusOne is null || updateApproximateYears is null) + result = null; + else + { + result = Path.Combine(minusOne, $"{personDisplayDirectoryName.Split('^')[0]}^{updateApproximateYears}"); + if (Directory.Exists(result)) + result = null; + else + Directory.Move(personDisplayDirectory, result); + } + return result; + } + + private static ReadOnlyCollection GetDirectoryAndTicksCollection(PeopleSettings peopleSettings, string? rootDirectory) + { + List results = []; + string directory; + DateTime dateTime; + string[] personKeyDirectories; + string[] personDisplayDirectoryNames; + string personKeyFormattedDirectoryName; + PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName personKeyFormattedAndKeyTicksAndDisplayDirectoryName; + foreach (string jLink in peopleSettings.JLinks) + { + if (rootDirectory is null) + continue; + directory = Path.Combine(rootDirectory, jLink); + if (!Directory.Exists(directory)) + continue; + personDisplayDirectoryNames = Directory.GetDirectories(directory, "*", SearchOption.TopDirectoryOnly); + foreach (string personDisplayDirectoryName in personDisplayDirectoryNames) + { + personKeyDirectories = Directory.GetDirectories(personDisplayDirectoryName, "*", SearchOption.TopDirectoryOnly); + foreach (string personKeyFormattedDirectory in personKeyDirectories) + { + personKeyFormattedDirectoryName = Path.GetFileName(personKeyFormattedDirectory); + if (personKeyFormattedDirectoryName.Length != peopleSettings.PersonBirthdayFormat.Length || !DateTime.TryParseExact(personKeyFormattedDirectoryName, peopleSettings.PersonBirthdayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime)) + continue; + personKeyFormattedAndKeyTicksAndDisplayDirectoryName = new(directory, dateTime.Ticks, personKeyFormattedDirectoryName); + results.Add(personKeyFormattedAndKeyTicksAndDisplayDirectoryName); + } + } + } + return results.AsReadOnly(); + } + + private static ReadOnlyCollection GetGenealogicalDataCommunicationDirectories(PeopleSettings peopleSettings) + { + ReadOnlyCollection results; + string? genealogicalDataCommunicationDirectory = Path.GetDirectoryName(peopleSettings.GenealogicalDataCommunicationFile); + results = GetDirectoryAndTicksCollection(peopleSettings, genealogicalDataCommunicationDirectory); + return results; + } + + private static string? TryToFind(PeopleSettings peopleSettings, string a2PeopleSingletonDirectory, List a2PeopleSingletonDirectories, string file, string path) + { + string? result; + string? pathName = Path.GetFileName(path); + string? group = Path.GetDirectoryName(path); + string? groupName = Path.GetFileName(group); + if (pathName is null || group is null || groupName is null) + result = null; + else + { + string[] matches; + matches = (from l in a2PeopleSingletonDirectories where l.DirectoryName == pathName select l.Directory).ToArray(); + if (matches.Length == 1) + result = matches.First(); + else + { + string pathNameSplitFirst = pathName.Split(peopleSettings.PersonCharacters).First(); + matches = (from l in a2PeopleSingletonDirectories where l.DirectoryNameSplitFirst == pathNameSplitFirst select l.Directory).ToArray(); + if (matches.Length == 1) + result = matches.First(); + else + { + string checkDirectory = Path.Combine(a2PeopleSingletonDirectory, groupName, pathName); + if (!Directory.Exists(checkDirectory)) + result = null; + else + result = checkDirectory; + } + } + if (!string.IsNullOrEmpty(result)) + { + try + { + WindowsShortcut windowsShortcut; + windowsShortcut = new() { Path = result }; + windowsShortcut.Save(file); + windowsShortcut.Dispose(); + } + catch (Exception) + { } + } + } + return result; + } + + private static ReadOnlyCollection GetJLinkResolvedDirectories(PeopleSettings peopleSettings, List resolvedDirectories) + { + List results = []; + DateTime dateTime; + string directoryName; + string[] directories; + PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName personKeyFormattedAndKeyTicksAndDisplayDirectoryName; + foreach (string resolvedDirectory in resolvedDirectories) + { + directories = Directory.GetDirectories(resolvedDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string directory in directories) + { + directoryName = Path.GetFileName(directory); + if (directoryName.Length != peopleSettings.PersonBirthdayFormat.Length || !DateTime.TryParseExact(directoryName, peopleSettings.PersonBirthdayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime)) + continue; + personKeyFormattedAndKeyTicksAndDisplayDirectoryName = new(resolvedDirectory, dateTime.Ticks, directoryName); + results.Add(personKeyFormattedAndKeyTicksAndDisplayDirectoryName); + } + } + if (results.Count != resolvedDirectories.Count) + results.Clear(); + return results.AsReadOnly(); + } + + internal static ReadOnlyCollection GetJLinkDirectories(ResultSettings resultSettings, PeopleSettings peopleSettings) + { + ReadOnlyCollection results; + string[] files; + string? foundPath; + int totalFiles = 0; + string checkDirectory; + WindowsShortcut windowsShortcut; + List resolvedDirectories = []; + string a2PeopleContentDirectory = Path.GetFullPath(IResult.GetResultsDateGroupDirectory(resultSettings, nameof(A2_People), resultSettings.ResultContent)); + if (!Directory.Exists(a2PeopleContentDirectory)) + _ = Directory.CreateDirectory(a2PeopleContentDirectory); + string a2PeopleSingletonDirectory = Path.GetFullPath(IResult.GetResultsDateGroupDirectory(resultSettings, nameof(A2_People), resultSettings.ResultSingleton)); + if (!Directory.Exists(a2PeopleSingletonDirectory)) + _ = Directory.CreateDirectory(a2PeopleSingletonDirectory); + if (string.IsNullOrEmpty(peopleSettings.GenealogicalDataCommunicationFile)) + results = GetDirectoryAndTicksCollection(peopleSettings, a2PeopleContentDirectory); + else + results = GetGenealogicalDataCommunicationDirectories(peopleSettings); + if (results.Count == 0 || results.Count < peopleSettings.JLinks.Length) + { + RecordB recordB; + List a2PeopleSingletonDirectories = []; + foreach (string directory in Directory.GetDirectories(a2PeopleSingletonDirectory, "*", SearchOption.AllDirectories)) + { + recordB = new(directory, Path.GetFileName(directory), Path.GetFileName(directory).Split(peopleSettings.PersonCharacters).First()); + a2PeopleSingletonDirectories.Add(recordB); + } + foreach (string directoryName in peopleSettings.JLinks) + { + checkDirectory = Path.Combine(a2PeopleContentDirectory, directoryName); + if (!Directory.Exists(checkDirectory)) + continue; + files = Directory.GetFiles(checkDirectory, "*.lnk", SearchOption.TopDirectoryOnly); + totalFiles += files.Length; + foreach (string file in files) + { + windowsShortcut = WindowsShortcut.Load(file); + if (windowsShortcut.Path is null) + continue; + if (Directory.Exists(windowsShortcut.Path)) + resolvedDirectories.Add(windowsShortcut.Path); + else + { + foundPath = TryToFind(peopleSettings, a2PeopleSingletonDirectory, a2PeopleSingletonDirectories, file, windowsShortcut.Path); + if (string.IsNullOrEmpty(foundPath)) + continue; + resolvedDirectories.Add(foundPath); + } + } + } + if (totalFiles == resolvedDirectories.Count) + results = GetJLinkResolvedDirectories(peopleSettings, resolvedDirectories); + else + { + resolvedDirectories.Clear(); + results = new([]); + } + } + return results; + } + + internal static ReadOnlyCollection GetJLinkResolvedDirectories(ResultSettings resultSettings, PeopleSettings peopleSettings) + { + ReadOnlyCollection jLinkResolvedDirectories; + if (!peopleSettings.JLinks.Where(l => !string.IsNullOrEmpty(l)).Any()) + jLinkResolvedDirectories = new([]); + else + { + jLinkResolvedDirectories = GetJLinkDirectories(resultSettings, peopleSettings); + if (jLinkResolvedDirectories.Count == 0) + throw new Exception(nameof(GetJLinkDirectories)); + } + return jLinkResolvedDirectories; + } + +} \ No newline at end of file diff --git a/People/Models/Stateless/ReadOnlyCollectionsLogic.cs b/People/Models/Stateless/ReadOnlyCollectionsLogic.cs new file mode 100644 index 0000000..563f85e --- /dev/null +++ b/People/Models/Stateless/ReadOnlyCollectionsLogic.cs @@ -0,0 +1,110 @@ +using System.Collections.ObjectModel; +using View_by_Distance.Metadata.Models; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.People.Models.Stateless.Methods; + +internal static class ReadOnlyCollectionsLogic +{ + + private static void SetPersonCollectionsAfterSetSkipCollections(PeopleSettings peopleSettings, ReadOnlyCollection personContainers, Dictionary personKeyFormattedToNewestPersonKeyFormatted, List personKeyFormattedCollection) + { + string personKeyFormatted; + string newestPersonKeyFormatted; + foreach (PersonContainer personContainer in personContainers) + { + if (personContainer.Key is null || personContainer.Birthdays is null || personContainer.Birthdays.Length == 0) + continue; + foreach (PersonBirthday personBirthday in personContainer.Birthdays) + { + personKeyFormatted = IPersonBirthday.GetFormatted(peopleSettings.PersonBirthdayFormat, personBirthday); + personKeyFormattedCollection.Add(personKeyFormatted); + if (personContainer.Birthdays.Length < 1) + continue; + newestPersonKeyFormatted = IPersonBirthday.GetFormatted(peopleSettings.PersonBirthdayFormat, personContainer.Key.Value); + _ = personKeyFormattedToNewestPersonKeyFormatted.TryAdd(personKeyFormatted, newestPersonKeyFormatted); + } + } + } + + private static void SetSkipCollections(ResultSettings resultSettings, ICompareSettings compareSettings, ReadOnlyCollection personContainers, Dictionary> skipCollection, Dictionary> skipNotSkipCollection, ReadOnlyCollection skipNotSkipDirectories) + { + string checkFile; + int? wholePercentages; + List distinct = []; + List distinctFiles = []; + List distinctFileName = []; + FilePathAndWholePercentages filePathAndWholePercentages; + bool skipNotSkipDirectoriesAny = skipNotSkipDirectories.Count > 0; + string a2PeopleSingletonDirectory = Path.GetFullPath(IResult.GetResultsDateGroupDirectory(resultSettings, nameof(A2_People), resultSettings.ResultSingleton)); + string[] checkDirectories = string.IsNullOrEmpty(a2PeopleSingletonDirectory) ? [] : (from l in skipNotSkipDirectories select Path.GetFullPath($"{a2PeopleSingletonDirectory}{l}")).ToArray(); + foreach (PersonContainer personContainer in personContainers) + { + foreach (FilePath personDisplayDirectoryAllFilePath in personContainer.DisplayDirectoryAllFilePaths) + { + if (personDisplayDirectoryAllFilePath.ExtensionLowered != compareSettings.FacesFileNameExtension) + continue; + if (distinctFiles.Contains(personDisplayDirectoryAllFilePath.FullName)) + continue; + distinctFiles.Add(personDisplayDirectoryAllFilePath.FullName); + distinct.Add(personDisplayDirectoryAllFilePath); + } + } + foreach (FilePath filePath in distinct) + { + if (distinctFileName.Contains(filePath.Name)) + { + checkFile = $"{filePath.FullName}.dup"; + if (File.Exists(checkFile)) + continue; + File.Move(filePath.FullName, checkFile); + continue; + } + if (filePath.Id is null) + continue; + wholePercentages = IMapping.GetWholePercentages(compareSettings, filePath); + if (wholePercentages is null) + continue; + if (!skipNotSkipDirectoriesAny || !checkDirectories.Any(filePath.FullName.StartsWith)) + { + if (!skipCollection.ContainsKey(filePath.Id.Value)) + skipCollection.Add(filePath.Id.Value, []); + filePathAndWholePercentages = new(filePath, wholePercentages.Value); + skipCollection[filePath.Id.Value].Add(filePathAndWholePercentages); + } + else + { + if (!skipNotSkipCollection.ContainsKey(filePath.Id.Value)) + skipNotSkipCollection.Add(filePath.Id.Value, []); + filePathAndWholePercentages = new(filePath, wholePercentages.Value); + skipNotSkipCollection[filePath.Id.Value].Add(filePathAndWholePercentages); + } + } + } + + internal static ReadOnlyCollections GetReadOnlyCollections(ResultSettings resultSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ReadOnlyCollection personContainers, ReadOnlyCollection personKeys) + { + ReadOnlyCollections result; + List personKeyFormattedCollection = []; + Dictionary> skipCollection = []; + Dictionary personKeyFormattedToNewestPersonKeyFormatted = []; + Dictionary> skipNotSkipCollection = []; + ReadOnlyCollection skipNotSkipDirectories = distanceSettings.SkipNotSkipDirectories.Where(l => !string.IsNullOrEmpty(l)).ToArray().AsReadOnly(); + SetSkipCollections(resultSettings, compareSettings, personContainers, skipCollection, skipNotSkipCollection, skipNotSkipDirectories); + SetPersonCollectionsAfterSetSkipCollections(peopleSettings, personContainers, personKeyFormattedToNewestPersonKeyFormatted, personKeyFormattedCollection); + ReadOnlyCollection jLinkResolvedDirectories = IPeople.GetJLinkResolvedDirectories(resultSettings, peopleSettings); + ReadOnlyCollection jLinkResolvedPersonKeys = jLinkResolvedDirectories.Select(l => l.KeyTicks).ToArray().AsReadOnly(); + result = new(jLinkResolvedPersonKeys, + personContainers, + personKeyFormattedCollection.AsReadOnly(), + personKeyFormattedToNewestPersonKeyFormatted.AsReadOnly(), + personKeys, + skipCollection.AsReadOnly(), + skipNotSkipCollection.AsReadOnly(), + skipNotSkipDirectories); + return result; + } + +} \ No newline at end of file diff --git a/Rename/Models/AppSettings.cs b/Rename/Models/AppSettings.cs index 82d9c99..4f3ddd7 100644 --- a/Rename/Models/AppSettings.cs +++ b/Rename/Models/AppSettings.cs @@ -1,12 +1,13 @@ +using Microsoft.Extensions.Configuration; using System.Text.Json; using System.Text.Json.Serialization; +using View_by_Distance.Shared.Models; namespace View_by_Distance.Rename.Models; -public record AppSettings(RenameConfiguration RenameConfiguration, - string Company, - int MaxDegreeOfParallelism, - bool RequireRootDirectoryExists) +public record AppSettings(ResultSettings ResultSettings, + MetadataSettings MetadataSettings, + RenameSettings RenameSettings) { public override string ToString() @@ -15,6 +16,42 @@ public record AppSettings(RenameConfiguration RenameConfiguration, return result; } + private static void Verify(AppSettings appSettings) + { + if (appSettings.RenameSettings.MaxDegreeOfParallelism > Environment.ProcessorCount) + throw new Exception("MaxDegreeOfParallelism must be =< Environment.ProcessorCount!"); + if (appSettings.RenameSettings.MaxDegreeOfParallelism > 1 && (appSettings.RenameSettings.InPlace || appSettings.RenameSettings.InPlaceMoveDirectory || appSettings.RenameSettings.InPlaceWithOriginalName)) + throw new NotSupportedException($"Change Settings: {nameof(appSettings.RenameSettings.InPlace)} or {nameof(appSettings.RenameSettings.InPlaceMoveDirectory)} or {nameof(appSettings.RenameSettings.MaxDegreeOfParallelism)}"); + if (appSettings.RenameSettings.InPlace && appSettings.RenameSettings.InPlaceMoveDirectory && appSettings.RenameSettings.InPlaceWithOriginalName) + throw new NotSupportedException($"Change Settings: {nameof(appSettings.RenameSettings.InPlace)} or {nameof(appSettings.RenameSettings.InPlaceMoveDirectory)} or {nameof(appSettings.RenameSettings.InPlaceWithOriginalName)}"); + } + + public static AppSettings Get(IConfigurationRoot configurationRoot) + { + AppSettings result; +#pragma warning disable IL3050, IL2026 + ResultSettings? resultSettings = configurationRoot.GetSection(nameof(ResultSettings)).Get(); + MetadataSettings? metadataSettings = configurationRoot.GetSection(nameof(MetadataSettings)).Get(); + RenameSettings? renameSettings = configurationRoot.GetSection(nameof(RenameSettings)).Get(); +#pragma warning restore IL3050, IL2026 + if (resultSettings is null || metadataSettings is null || renameSettings?.Company is null) + { + List paths = []; + foreach (IConfigurationProvider configurationProvider in configurationRoot.Providers) + { + if (configurationProvider is not Microsoft.Extensions.Configuration.Json.JsonConfigurationProvider jsonConfigurationProvider) + continue; + if (jsonConfigurationProvider.Source.FileProvider is not Microsoft.Extensions.FileProviders.PhysicalFileProvider physicalFileProvider) + continue; + paths.Add(physicalFileProvider.Root); + } + throw new NotSupportedException($"Not found!{Environment.NewLine}{string.Join(Environment.NewLine, paths.Distinct())}"); + } + result = new(resultSettings, metadataSettings, renameSettings); + Verify(result); + return result; + } + } [JsonSourceGenerationOptions(WriteIndented = true)] diff --git a/Rename/Models/Binder/.editorconfig b/Rename/Models/Binder/.editorconfig deleted file mode 100644 index 1c444cd..0000000 --- a/Rename/Models/Binder/.editorconfig +++ /dev/null @@ -1,2 +0,0 @@ -[*.cs] -csharp_preserve_single_line_statements = true \ No newline at end of file diff --git a/Rename/Models/Binder/AppSettings.cs b/Rename/Models/Binder/AppSettings.cs deleted file mode 100644 index f85aa63..0000000 --- a/Rename/Models/Binder/AppSettings.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Microsoft.Extensions.Configuration; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace View_by_Distance.Rename.Models.Binder; - -public class AppSettings -{ - - public string? Company { get; set; } - public string[]? ConfigurationDirectoryNames { get; set; } - public string? ConfigurationFileName { get; set; } - public int? ConfigurationSpecialFolder { get; set; } - public int? MaxDegreeOfParallelism { get; set; } - public bool? RequireRootDirectoryExists { get; set; } - - public override string ToString() - { - string result = JsonSerializer.Serialize(this, BinderAppSettingsSourceGenerationContext.Default.AppSettings); - return result; - } - - private static void PreVerify(IConfigurationRoot configurationRoot, AppSettings? appSettings) - { - if (appSettings?.Company is null) - { - List paths = []; - foreach (IConfigurationProvider configurationProvider in configurationRoot.Providers) - { - if (configurationProvider is not Microsoft.Extensions.Configuration.Json.JsonConfigurationProvider jsonConfigurationProvider) - continue; - if (jsonConfigurationProvider.Source.FileProvider is not Microsoft.Extensions.FileProviders.PhysicalFileProvider physicalFileProvider) - continue; - paths.Add(physicalFileProvider.Root); - } - throw new NotSupportedException($"Not found!{Environment.NewLine}{string.Join(Environment.NewLine, paths.Distinct())}"); - } - } - - private static void Verify(Models.AppSettings appSettings) - { - if (appSettings.MaxDegreeOfParallelism > 1 && (appSettings.RenameConfiguration.InPlace || appSettings.RenameConfiguration.InPlaceMoveDirectory || appSettings.RenameConfiguration.InPlaceWithOriginalName)) - throw new NotSupportedException($"Change configuration: {nameof(appSettings.RenameConfiguration.InPlace)} or {nameof(appSettings.RenameConfiguration.InPlaceMoveDirectory)} or {nameof(appSettings.MaxDegreeOfParallelism)}"); - if (appSettings.RenameConfiguration.InPlace && appSettings.RenameConfiguration.InPlaceMoveDirectory && appSettings.RenameConfiguration.InPlaceWithOriginalName) - throw new NotSupportedException($"Change configuration: {nameof(appSettings.RenameConfiguration.InPlace)} or {nameof(appSettings.RenameConfiguration.InPlaceMoveDirectory)} or {nameof(appSettings.RenameConfiguration.InPlaceWithOriginalName)}"); - } - - private static Models.AppSettings Get(AppSettings? appSettings, RenameConfiguration renameConfiguration) - { - Models.AppSettings result; - if (appSettings is null) throw new NullReferenceException(nameof(appSettings)); - if (appSettings.Company is null) throw new NullReferenceException(nameof(appSettings.Company)); - if (appSettings.MaxDegreeOfParallelism is null) throw new NullReferenceException(nameof(appSettings.MaxDegreeOfParallelism)); - if (appSettings.RequireRootDirectoryExists is null) throw new NullReferenceException(nameof(appSettings.RequireRootDirectoryExists)); - result = new(renameConfiguration, - appSettings.Company, - appSettings.MaxDegreeOfParallelism.Value, - appSettings.RequireRootDirectoryExists.Value); - Verify(result); - return result; - } - - private static Models.AppSettings Get(AppSettings? appSettings) - { - Models.AppSettings? result; - string? json; - if (appSettings is null || appSettings.ConfigurationFileName is null) - throw new NotSupportedException($"{nameof(appSettings.ConfigurationFileName)} must be set!"); - string jsonFile = Path.Combine(AppContext.BaseDirectory, appSettings.ConfigurationFileName); - if (File.Exists(jsonFile)) - json = File.ReadAllText(jsonFile); - else - { - json = null; - string applicationData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - List collection = [applicationData]; - if (appSettings?.ConfigurationDirectoryNames is not null) - collection.AddRange(appSettings.ConfigurationDirectoryNames); - if (appSettings?.ConfigurationFileName is not null) - collection.Add(appSettings.ConfigurationFileName); - jsonFile = Path.Combine(collection.ToArray()); - } - if (string.IsNullOrEmpty(json) && File.Exists(jsonFile)) - json = File.ReadAllText(jsonFile); - result = (string.IsNullOrEmpty(json) ? null : result = JsonSerializer.Deserialize(json, AppSettingsSourceGenerationContext.Default.AppSettings)) ?? - throw new NullReferenceException(nameof(Models.AppSettings)); - result = Get(appSettings, result.RenameConfiguration); - return result; - } - - public static Models.AppSettings Get(IConfigurationRoot configurationRoot) - { - Models.AppSettings result; -#pragma warning disable IL3050, IL2026 - AppSettings? appSettings = configurationRoot.Get(); -#pragma warning restore IL3050, IL2026 - PreVerify(configurationRoot, appSettings); - result = Get(appSettings); - return result; - } - -} - -[JsonSourceGenerationOptions(WriteIndented = true)] -[JsonSerializable(typeof(AppSettings))] -internal partial class BinderAppSettingsSourceGenerationContext : JsonSerializerContext -{ -} \ No newline at end of file diff --git a/Rename/Models/RenameConfiguration.cs b/Rename/Models/RenameConfiguration.cs deleted file mode 100644 index 4bc8a92..0000000 --- a/Rename/Models/RenameConfiguration.cs +++ /dev/null @@ -1,34 +0,0 @@ - -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace View_by_Distance.Rename.Models; - -public record RenameConfiguration(Shared.Models.MetadataConfiguration MetadataConfiguration, - string DefaultMaker, - bool ForceNewId, - string[] IgnoreExtensions, - bool InPlace, - bool InPlaceMoveDirectory, - bool InPlaceWithOriginalName, - bool OnlySaveIdentifiersToDisk, - string RelativePropertyCollectionFile, - string[] SidecarExtensions, - bool SkipIdFiles, - string[] ValidImageFormatExtensions, - string[] ValidVideoFormatExtensions) : Shared.Models.Properties.IRenameConfiguration -{ - - public override string ToString() - { - string result = JsonSerializer.Serialize(this, RenameConfigurationSourceGenerationContext.Default.RenameConfiguration); - return result; - } - -} - -[JsonSourceGenerationOptions(WriteIndented = true)] -[JsonSerializable(typeof(RenameConfiguration))] -internal partial class RenameConfigurationSourceGenerationContext : JsonSerializerContext -{ -} \ No newline at end of file diff --git a/Rename/Models/RenameSettings.cs b/Rename/Models/RenameSettings.cs new file mode 100644 index 0000000..64c6fcc --- /dev/null +++ b/Rename/Models/RenameSettings.cs @@ -0,0 +1,36 @@ + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Rename.Models; + +public record RenameSettings(string Company, + string DefaultMaker, + bool ForceNewId, + string[] IgnoreExtensions, + bool InPlace, + bool InPlaceMoveDirectory, + bool InPlaceWithOriginalName, + int MaxDegreeOfParallelism, + bool OnlySaveIdentifiersToDisk, + string RelativePropertyCollectionFile, + bool RequireRootDirectoryExists, + string[] SidecarExtensions, + bool SkipIdFiles, + string[] ValidImageFormatExtensions, + string[] ValidVideoFormatExtensions) : Shared.Models.Properties.IRenameSettings +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, RenameSettingsSourceGenerationContext.Default.RenameSettings); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(RenameSettings))] +internal partial class RenameSettingsSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Rename/Program.cs b/Rename/Program.cs index a160879..87bd86e 100644 --- a/Rename/Program.cs +++ b/Rename/Program.cs @@ -15,11 +15,7 @@ public class Program .AddEnvironmentVariables() .AddUserSecrets(); IConfigurationRoot configurationRoot = configurationBuilder.Build(); - AppSettings appSettings = Models.Binder.AppSettings.Get(configurationRoot); - if (appSettings.MaxDegreeOfParallelism > Environment.ProcessorCount) - throw new Exception("MaxDegreeOfParallelism must be =< Environment.ProcessorCount!"); - if (string.IsNullOrEmpty(appSettings.Company)) - throw new Exception("Company must have a value!"); + AppSettings appSettings = AppSettings.Get(configurationRoot); int silentIndex = args.IndexOf("s"); if (silentIndex > -1) args.RemoveAt(silentIndex); diff --git a/Rename/Rename.cs b/Rename/Rename.cs index 369c779..0a74b54 100644 --- a/Rename/Rename.cs +++ b/Rename/Rename.cs @@ -49,10 +49,10 @@ public partial class Rename : IRename, IDisposable GC.SuppressFinalize(this); } - ReadOnlyCollection IRename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(IRenameConfiguration renameConfiguration, FilePath filePath) + ReadOnlyCollection IRename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(IRenameSettings renameSettings, FilePath filePath) { List results = []; - bool isValidVideoFormatExtensions = renameConfiguration.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered); + bool isValidVideoFormatExtensions = renameSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered); if (isValidVideoFormatExtensions) { bool check; @@ -79,7 +79,7 @@ public partial class Rename : IRename, IDisposable Thread.Sleep(100); } } - return new(results); + return results.AsReadOnly(); } #pragma warning disable CA1416 @@ -117,22 +117,22 @@ public partial class Rename : IRename, IDisposable #pragma warning restore CA1416 - private void NonParallelismAndInPlace(RenameConfiguration renameConfiguration, ReadOnlyCollection ids, ExifDirectory exifDirectory, FileInfo fileInfo, FilePath filePath, bool fastForwardMovingPictureExpertsGroupUsed, ReadOnlyCollection sidecarFiles) + private void NonParallelismAndInPlace(AppSettings appSettings, ReadOnlyCollection ids, ExifDirectory exifDirectory, FileInfo fileInfo, FilePath filePath, bool fastForwardMovingPictureExpertsGroupUsed, ReadOnlyCollection sidecarFiles) { - if (exifDirectory.Id is null) + if (exifDirectory.FilePath.Id is null) throw new NotImplementedException(); int i = 0; + ToDo toDo; const string jpg = ".jpg"; const string jpeg = ".jpeg"; List toDoCollection = []; DateTime? dateTime = IDate.GetDateTimeOriginal(exifDirectory); ReadOnlyCollection keywords = IMetadata.GetKeywords(exifDirectory); - MetadataConfiguration metadataConfiguration = renameConfiguration.MetadataConfiguration; - bool hasIgnoreKeyword = metadataConfiguration.IgnoreRulesKeyWords.Any(keywords.Contains); + bool hasIgnoreKeyword = appSettings.MetadataSettings.IgnoreRulesKeyWords.Any(keywords.Contains); string checkFileExtension = filePath.ExtensionLowered == jpeg ? jpg : filePath.ExtensionLowered; bool hasDateTimeOriginal = dateTime is not null; - string paddedId = IId.GetPaddedId(metadataConfiguration, exifDirectory.Id.Value, hasIgnoreKeyword, hasDateTimeOriginal, i); - string checkDirectory = renameConfiguration.InPlaceWithOriginalName ? Path.Combine(filePath.DirectoryFullPath, filePath.FileNameFirstSegment) : filePath.DirectoryFullPath; + string paddedId = IId.GetPaddedId(appSettings.ResultSettings, appSettings.MetadataSettings, exifDirectory.FilePath.Id.Value, hasIgnoreKeyword, hasDateTimeOriginal, i); + string checkDirectory = appSettings.RenameSettings.InPlaceWithOriginalName ? Path.Combine(filePath.DirectoryFullPath, filePath.FileNameFirstSegment) : filePath.DirectoryFullPath; string checkFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}"); if (checkFile != filePath.FullName) { @@ -142,21 +142,22 @@ public partial class Rename : IRename, IDisposable if (File.Exists(checkFile)) throw new NotImplementedException(); } - toDoCollection.Add(new(checkDirectory, filePath, checkFile, JsonFile: false)); + toDo = new(checkDirectory, filePath, checkFile, JsonFile: false); + toDoCollection.Add(toDo); if (sidecarFiles.Count != 0) { - if (renameConfiguration.InPlace) - throw new NotSupportedException($"Must use {nameof(renameConfiguration.InPlaceWithOriginalName)} when sidecar file(s) are present!"); + if (appSettings.RenameSettings.InPlace) + throw new NotSupportedException($"Must use {nameof(appSettings.RenameSettings.InPlaceWithOriginalName)} when sidecar file(s) are present!"); dateTime ??= IDate.GetMinimum(exifDirectory); RecordB recordB = new(dateTime.Value, exifDirectory, fastForwardMovingPictureExpertsGroupUsed, filePath, sidecarFiles, hasDateTimeOriginal, hasIgnoreKeyword, fileInfo.FullName); - toDoCollection.AddRange(GetSidecarFiles(metadataConfiguration, recordB, [], checkDirectory, paddedId)); + toDoCollection.AddRange(GetSidecarFiles(appSettings, recordB, [], checkDirectory, paddedId)); } - _ = RenameFilesInDirectories(renameConfiguration, new(toDoCollection)); + _ = RenameFilesInDirectories(appSettings.RenameSettings, new(toDoCollection)); string jsonFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}.json"); File.Move(fileInfo.FullName, jsonFile, overwrite: true); - if (renameConfiguration.InPlaceWithOriginalName && ids.Count > 0) + if (appSettings.RenameSettings.InPlaceWithOriginalName && ids.Count > 0) { - string contains = ids.Contains(exifDirectory.Id.Value) ? "_ Exists _" : "_ New _"; + string contains = ids.Contains(exifDirectory.FilePath.Id.Value) ? "_ Exists _" : "_ New _"; string idCheck = Path.Combine(checkDirectory, contains, fastForwardMovingPictureExpertsGroupUsed ? "Video" : "Image"); if (!Directory.Exists(idCheck)) _ = Directory.CreateDirectory(idCheck); @@ -164,16 +165,16 @@ public partial class Rename : IRename, IDisposable } } - private List GetRecordACollection(ILogger? logger, RenameConfiguration renameConfiguration, IRename rename, ReadOnlyCollection ids, IEnumerable files, A_Metadata metadata) + private List GetRecordACollection(ILogger? logger, AppSettings appSettings, IRename rename, ReadOnlyCollection ids, IEnumerable files, A_Metadata metadata) { List results = []; int index = -1; + RecordA recordA; FileInfo fileInfo; FilePath filePath; string directoryName; ExifDirectory exifDirectory; List sidecarFiles; - DeterministicHashCode deterministicHashCode; bool fastForwardMovingPictureExpertsGroupUsed; FilePath? fastForwardMovingPictureExpertsGroupFilePath; ReadOnlyCollection? fastForwardMovingPictureExpertsGroupFiles; @@ -182,36 +183,34 @@ public partial class Rename : IRename, IDisposable { index += 1; rename.Tick(); - if (keyValuePair.Value.Count > 1 && !renameConfiguration.ForceNewId) + if (keyValuePair.Value.Count > 1 && !appSettings.RenameSettings.ForceNewId) { - if (renameConfiguration.InPlaceMoveDirectory) + if (appSettings.RenameSettings.InPlaceMoveDirectory) continue; - throw new NotSupportedException($"When sidecar files are present {nameof(renameConfiguration.ForceNewId)} must be true!"); + throw new NotSupportedException($"When sidecar files are present {nameof(appSettings.RenameSettings.ForceNewId)} must be true!"); } if (keyValuePair.Value.Count > 2) throw new NotSupportedException("Too many sidecar files!"); foreach (FileHolder fileHolder in keyValuePair.Value) { - if (renameConfiguration.SidecarExtensions.Contains(fileHolder.ExtensionLowered)) + if (appSettings.RenameSettings.SidecarExtensions.Contains(fileHolder.ExtensionLowered)) continue; - if (renameConfiguration.IgnoreExtensions.Contains(fileHolder.ExtensionLowered)) + if (appSettings.RenameSettings.IgnoreExtensions.Contains(fileHolder.ExtensionLowered)) continue; - filePath = FilePath.Get(renameConfiguration.MetadataConfiguration, fileHolder, index); - if (renameConfiguration.SkipIdFiles && filePath.Id is not null && (filePath.IsIntelligentIdFormat || filePath.SortOrder is not null)) + filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, index); + if (appSettings.RenameSettings.SkipIdFiles && filePath.Id is not null && (filePath.IsIntelligentIdFormat || filePath.SortOrder is not null)) continue; - if (!renameConfiguration.ForceNewId && filePath.Id is not null) + if (!appSettings.RenameSettings.ForceNewId && filePath.Id is not null) { fastForwardMovingPictureExpertsGroupFiles = null; - deterministicHashCode = new(null, filePath.Id, null); directoryName = Path.GetFileName(filePath.DirectoryFullPath); - if (renameConfiguration.InPlaceWithOriginalName || (renameConfiguration.InPlace && directoryName.EndsWith(filePath.Id.Value.ToString()))) + if (appSettings.RenameSettings.InPlaceWithOriginalName || (appSettings.RenameSettings.InPlace && directoryName.EndsWith(filePath.Id.Value.ToString()))) continue; } else { - fastForwardMovingPictureExpertsGroupFiles = rename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(renameConfiguration, filePath); - fastForwardMovingPictureExpertsGroupFilePath = fastForwardMovingPictureExpertsGroupFiles.Count == 0 ? null : FilePath.Get(renameConfiguration.MetadataConfiguration, FileHolder.Get(fastForwardMovingPictureExpertsGroupFiles[0]), index); - deterministicHashCode = fastForwardMovingPictureExpertsGroupFilePath is null ? rename.GetDeterministicHashCode(filePath) : rename.GetDeterministicHashCode(fastForwardMovingPictureExpertsGroupFilePath); + fastForwardMovingPictureExpertsGroupFiles = rename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(appSettings.RenameSettings, filePath); + fastForwardMovingPictureExpertsGroupFilePath = fastForwardMovingPictureExpertsGroupFiles.Count == 0 ? null : FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, FileHolder.Get(fastForwardMovingPictureExpertsGroupFiles[0]), index); } sidecarFiles = []; for (int i = 0; i < keyValuePair.Value.Count; i++) @@ -221,7 +220,7 @@ public partial class Rename : IRename, IDisposable sidecarFiles.Add(keyValuePair.Value[i]); } try - { (fileInfo, exifDirectory) = metadata.GetMetadataCollection(renameConfiguration.MetadataConfiguration, filePath, deterministicHashCode); } + { (fileInfo, exifDirectory) = metadata.GetMetadataCollection(appSettings.ResultSettings, appSettings.MetadataSettings, filePath); } catch (Exception) { logger?.LogWarning("<{filePath}>", filePath.FullName); @@ -233,19 +232,21 @@ public partial class Rename : IRename, IDisposable foreach (string fastForwardMovingPictureExpertsGroupFile in fastForwardMovingPictureExpertsGroupFiles) File.Delete(fastForwardMovingPictureExpertsGroupFile); } - if (renameConfiguration.InPlace || renameConfiguration.InPlaceWithOriginalName) - NonParallelismAndInPlace(renameConfiguration, ids, exifDirectory, fileInfo, filePath, fastForwardMovingPictureExpertsGroupUsed, new(sidecarFiles)); - if (!fastForwardMovingPictureExpertsGroupUsed && renameConfiguration.InPlaceMoveDirectory && renameConfiguration.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered)) + if (appSettings.RenameSettings.InPlace || appSettings.RenameSettings.InPlaceWithOriginalName) + NonParallelismAndInPlace(appSettings, ids, exifDirectory, fileInfo, filePath, fastForwardMovingPictureExpertsGroupUsed, new(sidecarFiles)); + if (!fastForwardMovingPictureExpertsGroupUsed && appSettings.RenameSettings.InPlaceMoveDirectory && appSettings.RenameSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered)) fastForwardMovingPictureExpertsGroupUsed = true; - results.Add(new(exifDirectory, fastForwardMovingPictureExpertsGroupUsed, fileInfo, filePath, new(sidecarFiles))); + recordA = new(exifDirectory, fastForwardMovingPictureExpertsGroupUsed, fileInfo, filePath, new(sidecarFiles)); + results.Add(recordA); } } return results; } - private static ReadOnlyCollection GetRecordBCollection(MetadataConfiguration metadataConfiguration, List recordACollection) + private static ReadOnlyCollection GetRecordBCollection(AppSettings appSettings, List recordACollection) { List results = []; + RecordB recordB; DateTime? dateTime; bool hasIgnoreKeyword; bool hasDateTimeOriginal; @@ -256,52 +257,54 @@ public partial class Rename : IRename, IDisposable hasDateTimeOriginal = dateTime is not null; dateTime ??= IDate.GetMinimum(recordA.ExifDirectory); keywords = IMetadata.GetKeywords(recordA.ExifDirectory); - hasIgnoreKeyword = metadataConfiguration.IgnoreRulesKeyWords.Any(l => keywords.Contains(l)); - results.Add(new(dateTime.Value, recordA.ExifDirectory, recordA.FastForwardMovingPictureExpertsGroupUsed, recordA.FilePath, recordA.SidecarFiles, hasDateTimeOriginal, hasIgnoreKeyword, recordA.FileInfo.FullName)); + hasIgnoreKeyword = appSettings.MetadataSettings.IgnoreRulesKeyWords.Any(l => keywords.Contains(l)); + recordB = new(dateTime.Value, recordA.ExifDirectory, recordA.FastForwardMovingPictureExpertsGroupUsed, recordA.FilePath, recordA.SidecarFiles, hasDateTimeOriginal, hasIgnoreKeyword, recordA.FileInfo.FullName); + results.Add(recordB); } - return new(results); + return results.AsReadOnly(); } private ReadOnlyCollection GetRecordBCollection(ILogger? logger, AppSettings appSettings, IRename rename, ReadOnlyCollection ids, DirectoryInfo directoryInfo) { ReadOnlyCollection results; + RecordA recordA; List recordACollection = []; - RenameConfiguration renameConfiguration = appSettings.RenameConfiguration; - A_Metadata metadata = new(renameConfiguration.MetadataConfiguration); - int appSettingsMaxDegreeOfParallelism = appSettings.MaxDegreeOfParallelism; + A_Metadata metadata = new(appSettings.ResultSettings, appSettings.MetadataSettings); + int appSettingsMaxDegreeOfParallelism = appSettings.RenameSettings.MaxDegreeOfParallelism; IEnumerable files = appSettingsMaxDegreeOfParallelism == 1 ? Directory.GetFiles(directoryInfo.FullName, "*", SearchOption.AllDirectories) : Directory.EnumerateFiles(directoryInfo.FullName, "*", SearchOption.AllDirectories); int filesCount = appSettingsMaxDegreeOfParallelism == 1 ? files.Count() : 123000; _ProgressBar = new(filesCount, "EnumerateFiles load", new ProgressBarOptions() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }); if (appSettingsMaxDegreeOfParallelism == 1) - recordACollection.AddRange(GetRecordACollection(logger, renameConfiguration, rename, ids, files, metadata)); + recordACollection.AddRange(GetRecordACollection(logger, appSettings, rename, ids, files, metadata)); else { List distinct = []; - List<(bool, FilePath, FileInfo, ExifDirectory, ReadOnlyCollection)> collection = []; + List metadataGroups = []; ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = appSettingsMaxDegreeOfParallelism }; - files.AsParallel().ForAll(IMetadata.SetExifDirectoryCollection(rename, renameConfiguration, metadata, distinct, collection)); + files.AsParallel().ForAll(IMetadata.SetExifDirectoryCollection(rename, appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.RenameSettings, metadata, distinct, metadataGroups)); if (_ProgressBar.CurrentTick != recordACollection.Count) throw new NotSupportedException(); - foreach ((bool fastForwardMovingPictureExpertsGroupUsed, FilePath filePath, FileInfo fileInfo, ExifDirectory exifDirectory, ReadOnlyCollection sidecarFiles) in collection) + foreach (MetadataGroup metadataGroup in metadataGroups) { - if (fastForwardMovingPictureExpertsGroupUsed || !renameConfiguration.InPlaceMoveDirectory || !renameConfiguration.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered)) - recordACollection.Add(new(exifDirectory, fastForwardMovingPictureExpertsGroupUsed, fileInfo, filePath, sidecarFiles)); + if (metadataGroup.FastForwardMovingPictureExpertsGroupUsed || !appSettings.RenameSettings.InPlaceMoveDirectory || !appSettings.RenameSettings.ValidVideoFormatExtensions.Contains(metadataGroup.FilePath.ExtensionLowered)) + recordA = new(metadataGroup.ExifDirectory, metadataGroup.FastForwardMovingPictureExpertsGroupUsed, metadataGroup.FileInfo, metadataGroup.FilePath, metadataGroup.SidecarFiles); else - recordACollection.Add(new(exifDirectory, FastForwardMovingPictureExpertsGroupUsed: true, fileInfo, filePath, sidecarFiles)); + recordA = new(metadataGroup.ExifDirectory, FastForwardMovingPictureExpertsGroupUsed: true, metadataGroup.FileInfo, metadataGroup.FilePath, metadataGroup.SidecarFiles); + recordACollection.Add(recordA); } } _ProgressBar.Dispose(); - results = GetRecordBCollection(renameConfiguration.MetadataConfiguration, recordACollection); + results = GetRecordBCollection(appSettings, recordACollection); return results; } - private static void VerifyIntMinValueLength(MetadataConfiguration metadataConfiguration, ReadOnlyCollection recordBCollection) + private static void VerifyIntMinValueLength(MetadataSettings metadataSettings, ReadOnlyCollection recordBCollection) { foreach (RecordB recordB in recordBCollection) { - if (recordB.ExifDirectory.Id is null) + if (recordB.ExifDirectory.FilePath.Id is null) continue; - if (metadataConfiguration.IntMinValueLength < recordB.ExifDirectory.Id.Value.ToString().Length) + if (metadataSettings.IntMinValueLength < recordB.ExifDirectory.FilePath.Id.Value.ToString().Length) throw new NotSupportedException(); } } @@ -312,7 +315,7 @@ public partial class Rename : IRename, IDisposable private static string GetDirectoryName(string year, string tfw, string prefix, string? splat, int seasonValue, string seasonName, string makerSplit) => splat is null ? $"{prefix}{year} {tfw}{year}.{seasonValue} {seasonName}{makerSplit}" : $"{prefix}{year} {tfw}{year}{splat}"; - private static string? GetCheckDirectory(RenameConfiguration renameConfiguration, RecordB record, ReadOnlyCollection ids, bool multipleDirectoriesWithFiles, string paddedId) + private static string? GetCheckDirectory(AppSettings appSettings, RecordB record, ReadOnlyCollection ids, bool multipleDirectoriesWithFiles, string paddedId) { string? result; string year = record.DateTime.Year.ToString(); @@ -322,7 +325,7 @@ public partial class Rename : IRename, IDisposable else { (bool? isWrongYear, string[] years) = IDate.IsWrongYear(record.FilePath, record.ExifDirectory); - if (renameConfiguration.InPlaceMoveDirectory && !record.FilePath.FileNameFirstSegment.Contains(paddedId)) + if (appSettings.RenameSettings.InPlaceMoveDirectory && !record.FilePath.FileNameFirstSegment.Contains(paddedId)) result = null; else { @@ -331,9 +334,9 @@ public partial class Rename : IRename, IDisposable string[] segments = checkDirectoryName.Split(years, StringSplitOptions.None); string? splat = checkDirectoryName[^3..][1] == '!' ? checkDirectoryName[^3..] : null; (int seasonValue, string seasonName) = IDate.GetSeason(record.DateTime.DayOfYear); - string rootDirectory = renameConfiguration.MetadataConfiguration.ResultConfiguration.RootDirectory; - string contains = record.ExifDirectory.Id is null || ids.Contains(record.ExifDirectory.Id.Value) ? "_ Exists _" : "_ New-Destination _"; - string makerSplit = string.IsNullOrEmpty(maker) ? string.IsNullOrEmpty(renameConfiguration.DefaultMaker) ? string.Empty : renameConfiguration.DefaultMaker : $" {maker.Split(' ')[0]}"; + string rootDirectory = appSettings.ResultSettings.RootDirectory; + string contains = record.ExifDirectory.FilePath.Id is null || ids.Contains(record.ExifDirectory.FilePath.Id.Value) ? "_ Exists _" : "_ New-Destination _"; + string makerSplit = string.IsNullOrEmpty(maker) ? string.IsNullOrEmpty(appSettings.RenameSettings.DefaultMaker) ? string.Empty : appSettings.RenameSettings.DefaultMaker : $" {maker.Split(' ')[0]}"; string directoryName = GetDirectoryName(year, tfw, segments[0], splat, seasonValue, seasonName, makerSplit); result = Path.GetFullPath(Path.Combine(rootDirectory, contains, directoryName)); } @@ -341,16 +344,17 @@ public partial class Rename : IRename, IDisposable return result; } - private static List GetSidecarFiles(MetadataConfiguration metadataConfiguration, RecordB record, List distinct, string checkDirectory, string paddedId) + private static List GetSidecarFiles(AppSettings appSettings, RecordB record, List distinct, string checkDirectory, string paddedId) { List results = []; + ToDo toDo; string checkFile; FilePath filePath; string checkFileExtension; foreach (FileHolder fileHolder in record.SidecarFiles) { checkFileExtension = fileHolder.ExtensionLowered; - filePath = FilePath.Get(metadataConfiguration, fileHolder, index: null); + filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, index: null); checkFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}"); if (checkFile == filePath.FullName) continue; @@ -363,16 +367,17 @@ public partial class Rename : IRename, IDisposable if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); - results.Add(new(checkDirectory, filePath, checkFile, JsonFile: false)); + toDo = new(checkDirectory, filePath, checkFile, JsonFile: false); + results.Add(toDo); } return results; } - private static bool? GetDirectoryCheck(RenameConfiguration renameConfiguration) + private static bool? GetDirectoryCheck(ResultSettings resultSettings) { bool? result = null; IEnumerable files; - string[] directories = Directory.GetDirectories(renameConfiguration.MetadataConfiguration.ResultConfiguration.RootDirectory, "*", SearchOption.TopDirectoryOnly); + string[] directories = Directory.GetDirectories(resultSettings.RootDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string directory in directories) { files = Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories); @@ -390,9 +395,10 @@ public partial class Rename : IRename, IDisposable return result; } - private static ReadOnlyCollection GetToDoCollection(RenameConfiguration renameConfiguration, ReadOnlyCollection ids, ReadOnlyCollection recordBCollection) + private static ReadOnlyCollection GetToDoCollection(AppSettings appSettings, ReadOnlyCollection ids, ReadOnlyCollection recordBCollection) { List results = []; + ToDo toDo; RecordB record; string jsonFile; string paddedId; @@ -406,18 +412,17 @@ public partial class Rename : IRename, IDisposable List distinct = []; const string jpeg = ".jpeg"; string jsonFileSubDirectory; - bool? directoryCheck = GetDirectoryCheck(renameConfiguration); - MetadataConfiguration metadataConfiguration = renameConfiguration.MetadataConfiguration; - VerifyIntMinValueLength(metadataConfiguration, recordBCollection); + bool? directoryCheck = GetDirectoryCheck(appSettings.ResultSettings); + VerifyIntMinValueLength(appSettings.MetadataSettings, recordBCollection); bool multipleDirectoriesWithFiles = directoryCheck is not null && directoryCheck.Value; - ReadOnlyCollection sorted = new((from l in recordBCollection orderby l.DateTime select l).ToArray()); + ReadOnlyCollection sorted = (from l in recordBCollection orderby l.DateTime select l).ToArray().AsReadOnly(); for (int i = 0; i < sorted.Count; i++) { record = sorted[i]; - if (record.ExifDirectory.Id is null) + if (record.ExifDirectory.FilePath.Id is null) continue; - paddedId = IId.GetPaddedId(metadataConfiguration, record.ExifDirectory.Id.Value, record.HasIgnoreKeyword, record.HasDateTimeOriginal, i); - checkDirectory = GetCheckDirectory(renameConfiguration, record, ids, multipleDirectoriesWithFiles, paddedId); + paddedId = IId.GetPaddedId(appSettings.ResultSettings, appSettings.MetadataSettings, record.ExifDirectory.FilePath.Id.Value, record.HasIgnoreKeyword, record.HasDateTimeOriginal, i); + checkDirectory = GetCheckDirectory(appSettings, record, ids, multipleDirectoriesWithFiles, paddedId); if (string.IsNullOrEmpty(checkDirectory)) continue; checkFileExtension = record.FilePath.ExtensionLowered == jpeg ? jpg : record.FilePath.ExtensionLowered; @@ -431,23 +436,25 @@ public partial class Rename : IRename, IDisposable if (File.Exists(checkFile)) continue; } - (directoryName, _) = IPath.GetDirectoryNameAndIndex(metadataConfiguration.ResultConfiguration, record.ExifDirectory.Id.Value); - jsonFile = Path.Combine(jsonFileSubDirectory, directoryName, $"{record.ExifDirectory.Id.Value}{checkFileExtension}.json"); + (directoryName, _) = IPath.GetDirectoryNameAndIndex(appSettings.ResultSettings, record.ExifDirectory.FilePath.Id.Value); + jsonFile = Path.Combine(jsonFileSubDirectory, directoryName, $"{record.ExifDirectory.FilePath.Id.Value}{checkFileExtension}.json"); if (record.JsonFile != jsonFile) { fileHolder = FileHolder.Get(record.JsonFile); - filePath = FilePath.Get(metadataConfiguration, fileHolder, index: null); - results.Add(new(null, filePath, jsonFile, JsonFile: true)); + filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, index: null); + toDo = new(null, filePath, jsonFile, JsonFile: true); + results.Add(toDo); } if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); - results.Add(new(checkDirectory, record.FilePath, checkFile, JsonFile: false)); + toDo = new(checkDirectory, record.FilePath, checkFile, JsonFile: false); + results.Add(toDo); if (record.SidecarFiles.Count == 0) continue; - results.AddRange(GetSidecarFiles(metadataConfiguration, record, distinct, checkDirectory, paddedId)); + results.AddRange(GetSidecarFiles(appSettings, record, distinct, checkDirectory, paddedId)); } - return new(results); + return results.AsReadOnly(); } private static void VerifyDirectories(ReadOnlyCollection toDoCollection) @@ -463,11 +470,11 @@ public partial class Rename : IRename, IDisposable } } - private ReadOnlyCollection RenameFilesInDirectories(RenameConfiguration renameConfiguration, ReadOnlyCollection toDoCollection) + private ReadOnlyCollection RenameFilesInDirectories(RenameSettings renameSettings, ReadOnlyCollection toDoCollection) { List results = []; VerifyDirectories(toDoCollection); - bool useProgressBar = !renameConfiguration.InPlace && !renameConfiguration.InPlaceWithOriginalName; + bool useProgressBar = !renameSettings.InPlace && !renameSettings.InPlaceWithOriginalName; if (useProgressBar) _ProgressBar = new(toDoCollection.Count, "Move Files", new ProgressBarOptions() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }); foreach (ToDo toDo in toDoCollection) @@ -498,61 +505,58 @@ public partial class Rename : IRename, IDisposable } if (useProgressBar) _ProgressBar?.Dispose(); - return new(results); + return results.AsReadOnly(); } - private static void SaveIdentifiersToDisk(long ticks, RenameConfiguration renameConfiguration, ReadOnlyCollection recordBCollection) + private static void SaveIdentifiersToDisk(long ticks, AppSettings appSettings, ReadOnlyCollection recordBCollection) { string paddedId; Identifier identifier; List identifiers = []; - MetadataConfiguration metadataConfiguration = renameConfiguration.MetadataConfiguration; - string aMetadataCollectionDirectory = IResult.GetResultsDateGroupDirectory(metadataConfiguration.ResultConfiguration, nameof(A_Metadata), metadataConfiguration.ResultConfiguration.ResultCollection); + string aMetadataCollectionDirectory = IResult.GetResultsDateGroupDirectory(appSettings.ResultSettings, nameof(A_Metadata), appSettings.ResultSettings.ResultCollection); foreach (RecordB record in recordBCollection) { - if (record.ExifDirectory.Id is null) + if (record.ExifDirectory.FilePath.Id is null) continue; - paddedId = IId.GetPaddedId(renameConfiguration.MetadataConfiguration, record.ExifDirectory.Id.Value, record.HasIgnoreKeyword, record.HasDateTimeOriginal, index: null); - identifier = new([], record.HasDateTimeOriginal, record.ExifDirectory.Id.Value, record.FilePath.Length, paddedId, record.DateTime.Ticks); + paddedId = IId.GetPaddedId(appSettings.ResultSettings, appSettings.MetadataSettings, record.ExifDirectory.FilePath.Id.Value, record.HasIgnoreKeyword, record.HasDateTimeOriginal, index: null); + identifier = new([], record.HasDateTimeOriginal, record.ExifDirectory.FilePath.Id.Value, record.FilePath.Length, paddedId, record.DateTime.Ticks); identifiers.Add(identifier); } string json = JsonSerializer.Serialize(identifiers.OrderBy(l => l.PaddedId).ToArray(), IdentifierCollectionSourceGenerationContext.Default.IdentifierArray); _ = IPath.WriteAllText(Path.Combine(aMetadataCollectionDirectory, $"{ticks}.json"), json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null); } - private static ReadOnlyCollection GetIds(RenameConfiguration renameConfiguration) + private static ReadOnlyCollection GetIds(RenameSettings renameSettings) { ReadOnlyCollection results; - string? propertyCollectionFile = string.IsNullOrEmpty(renameConfiguration.RelativePropertyCollectionFile) ? null : renameConfiguration.RelativePropertyCollectionFile; + string? propertyCollectionFile = string.IsNullOrEmpty(renameSettings.RelativePropertyCollectionFile) ? null : renameSettings.RelativePropertyCollectionFile; string? json = !File.Exists(propertyCollectionFile) ? null : File.ReadAllText(propertyCollectionFile); Identifier[]? identifiers = json is null ? null : JsonSerializer.Deserialize(json, IdentifierCollectionSourceGenerationContext.Default.IdentifierArray); - if (identifiers is null && !string.IsNullOrEmpty(renameConfiguration.RelativePropertyCollectionFile)) - throw new Exception($"Invalid {nameof(renameConfiguration.RelativePropertyCollectionFile)}"); + if (identifiers is null && !string.IsNullOrEmpty(renameSettings.RelativePropertyCollectionFile)) + throw new Exception($"Invalid {nameof(renameSettings.RelativePropertyCollectionFile)}"); results = identifiers is null ? new([]) : new((from l in identifiers select l.Id).ToArray()); return results; } private void RenameWork(ILogger? logger, AppSettings appSettings, IRename rename, long ticks) { - ReadOnlyCollection ids = GetIds(appSettings.RenameConfiguration); - RenameConfiguration renameConfiguration = appSettings.RenameConfiguration; - MetadataConfiguration metadataConfiguration = renameConfiguration.MetadataConfiguration; - _ = IPath.DeleteEmptyDirectories(metadataConfiguration.ResultConfiguration.RootDirectory); - DirectoryInfo directoryInfo = new(Path.GetFullPath(metadataConfiguration.ResultConfiguration.RootDirectory)); + ReadOnlyCollection ids = GetIds(appSettings.RenameSettings); + _ = IPath.DeleteEmptyDirectories(appSettings.ResultSettings.RootDirectory); + DirectoryInfo directoryInfo = new(Path.GetFullPath(appSettings.ResultSettings.RootDirectory)); logger?.LogInformation("{Ticks} {RootDirectory}", ticks, directoryInfo.FullName); ReadOnlyCollection recordBCollection = GetRecordBCollection(logger, appSettings, rename, ids, directoryInfo); - SaveIdentifiersToDisk(ticks, renameConfiguration, recordBCollection); - if (renameConfiguration.InPlace || renameConfiguration.InPlaceWithOriginalName) + SaveIdentifiersToDisk(ticks, appSettings, recordBCollection); + if (appSettings.RenameSettings.InPlace || appSettings.RenameSettings.InPlaceWithOriginalName) { if (recordBCollection.Count > 0) recordBCollection = new([]); - string aMetadataSingletonDirectory = IResult.GetResultsGroupDirectory(metadataConfiguration.ResultConfiguration, nameof(A_Metadata)); + string aMetadataSingletonDirectory = IResult.GetResultsGroupDirectory(appSettings.ResultSettings, nameof(A_Metadata)); _ = IPath.DeleteEmptyDirectories(aMetadataSingletonDirectory); } - if (!renameConfiguration.OnlySaveIdentifiersToDisk) + if (!appSettings.RenameSettings.OnlySaveIdentifiersToDisk) { - ReadOnlyCollection toDoCollection = GetToDoCollection(renameConfiguration, ids, recordBCollection); - ReadOnlyCollection lines = RenameFilesInDirectories(renameConfiguration, toDoCollection); + ReadOnlyCollection toDoCollection = GetToDoCollection(appSettings, ids, recordBCollection); + ReadOnlyCollection lines = RenameFilesInDirectories(appSettings.RenameSettings, toDoCollection); if (lines.Count != 0) { File.WriteAllLines($"D:/Tmp/Phares/{DateTime.Now.Ticks}.tsv", lines); diff --git a/Shared/Models/DistanceSettings.cs b/Shared/Models/DistanceSettings.cs new file mode 100644 index 0000000..2f7b975 --- /dev/null +++ b/Shared/Models/DistanceSettings.cs @@ -0,0 +1,44 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record DistanceSettings(bool DistanceMoveUnableToMatch, + int FaceAreaPermyriad, + int FaceConfidencePercent, + int FaceDistancePermyriad, + string FocusDirectory, + string FocusModel, + string LinkedAlpha, + string LocationContainerDebugDirectory, + string LocationContainerDirectoryPattern, + int LocationDigits, + int LocationFactor, + string MappingDefaultName, + int[] RangeDaysDeltaTolerance, + float[] RangeDistanceTolerance, + float[] RangeFaceAreaTolerance, + float[] RangeFaceConfidence, + bool ReMap, + bool SaveIndividually, + bool SaveSortingWithoutPerson, + string[] SkipNotSkipDirectories, + int SkipOlderThanDays, + int SkipPersonWithMoreThen, + int SortingMaximumPerFaceShouldBeHigh, + bool UseExtraPersonKeyCheck) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, DistanceSettingsSourceGenerationContext.Default.DistanceSettings); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(DistanceSettings))] +internal partial class DistanceSettingsSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/ExifDirectory.cs b/Shared/Models/ExifDirectory.cs index ffb15e5..7953c92 100644 --- a/Shared/Models/ExifDirectory.cs +++ b/Shared/Models/ExifDirectory.cs @@ -4,15 +4,16 @@ using System.Text.Json.Serialization; namespace View_by_Distance.Shared.Models; public record ExifDirectory(AviDirectory[] AviDirectories, + object? Encoding, ExifDirectoryBase[] ExifBaseDirectories, FileMetadataDirectory[] FileMetadataDirectories, + FilePath FilePath, GifHeaderDirectory[] GifHeaderDirectories, GpsDirectory[] GpsDirectories, int? Height, - int? Id, JpegDirectory[] JpegDirectories, MakernoteDirectory[] MakernoteDirectories, - string OriginalFileName, + PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, PhotoshopDirectory[] PhotoshopDirectories, PngDirectory[] PngDirectories, QuickTimeMovieHeaderDirectory[] QuickTimeMovieHeaderDirectories, @@ -27,6 +28,25 @@ public record ExifDirectory(AviDirectory[] AviDirectories, return result; } + public static ExifDirectory Get(object encoding, ExifDirectory e) => + new(e.AviDirectories, + encoding, + e.ExifBaseDirectories, + e.FileMetadataDirectories, + e.FilePath, + e.GifHeaderDirectories, + e.GpsDirectories, + e.Height, + e.JpegDirectories, + e.MakernoteDirectories, + e.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, + e.PhotoshopDirectories, + e.PngDirectories, + e.QuickTimeMovieHeaderDirectories, + e.QuickTimeTrackHeaderDirectories, + e.WebPDirectories, + e.Width); + } [JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] diff --git a/Shared/Models/FaceDistance.cs b/Shared/Models/FaceDistance.cs new file mode 100644 index 0000000..2507797 --- /dev/null +++ b/Shared/Models/FaceDistance.cs @@ -0,0 +1,53 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record class FaceDistance : Properties.IFaceDistance +{ + + public int? ConfidencePercent { init; get; } + public DateTime DateTimeOriginalThenMinimumDateTime { init; get; } + public object? Encoding { init; get; } + public MappingFromFilterPost? MappingFromFilterPost { init; get; } + public int? Id { init; get; } + public bool? IsWrongYear { init; get; } + public double? Length { init; get; } + public int? WholePercentages { init; get; } + + [JsonConstructor] + public FaceDistance(int? confidencePercent, DateTime dateTimeOriginalThenMinimumDateTime, object? encoding, MappingFromFilterPost? mappingFromFilterPost, int? id, bool? isWrongYear, double? length, int? wholePercentages) + { + ConfidencePercent = confidencePercent; + DateTimeOriginalThenMinimumDateTime = dateTimeOriginalThenMinimumDateTime; + Encoding = encoding; + MappingFromFilterPost = mappingFromFilterPost; + Id = id; + IsWrongYear = isWrongYear; + Length = length; + WholePercentages = wholePercentages; + } + + public FaceDistance(int? confidencePercent, DateTime dateTimeOriginalThenMinimumDateTime, object? encoding, MappingFromFilterPost? mappingFromFilterPost, int id, bool? isWrongYear, int? wholePercentages) : + this(confidencePercent, dateTimeOriginalThenMinimumDateTime, encoding, mappingFromFilterPost, id, isWrongYear, null, wholePercentages) + { } + + public FaceDistance(FaceDistance faceDistance, double length) : + this(faceDistance.ConfidencePercent, faceDistance.DateTimeOriginalThenMinimumDateTime, null, faceDistance.MappingFromFilterPost, faceDistance.Id, faceDistance.IsWrongYear, length, faceDistance.WholePercentages) + { } + + public FaceDistance(object encoding) => Encoding = encoding; + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, FaceDistanceSourceGenerationContext.Default.FaceDistance); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(FaceDistance))] +internal partial class FaceDistanceSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/FacePartAndFacePointArray.cs b/Shared/Models/FacePartAndFacePointArray.cs new file mode 100644 index 0000000..eeddd65 --- /dev/null +++ b/Shared/Models/FacePartAndFacePointArray.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using View_by_Distance.Shared.Models.Stateless; + +namespace View_by_Distance.Shared.Models; + +public record FacePartAndFacePointArray(FacePart FacePart, FacePoint[] FacePoints) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, FacePartAndFacePointArraySourceGenerationContext.Default.FacePartAndFacePointArray); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(FacePartAndFacePointArray))] +public partial class FacePartAndFacePointArraySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/FaceRecognitionGroup.cs b/Shared/Models/FaceRecognitionGroup.cs new file mode 100644 index 0000000..9eadb02 --- /dev/null +++ b/Shared/Models/FaceRecognitionGroup.cs @@ -0,0 +1,22 @@ +using System.Collections.ObjectModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record FaceRecognitionGroup(Location Location, ReadOnlyCollection FaceEncodings, ReadOnlyCollection> FaceParts) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, FaceRecognitionGroupSourceGenerationContext.Default.FaceRecognitionGroup); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(FaceRecognitionGroup))] +public partial class FaceRecognitionGroupSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/FileHolder.cs b/Shared/Models/FileHolder.cs index 62e2659..e777014 100644 --- a/Shared/Models/FileHolder.cs +++ b/Shared/Models/FileHolder.cs @@ -51,7 +51,8 @@ public record FileHolder(DateTime? CreationTime, public static FileHolder Get(FilePath filePath, int? id) { FileHolder result; - result = new(new(filePath.CreationTicks), + DateTime dateTime = new(filePath.CreationTicks); + result = new(dateTime, filePath.DirectoryFullPath, true, filePath.ExtensionLowered, diff --git a/Shared/Models/FilePath.cs b/Shared/Models/FilePath.cs index 20dbde3..51d2c46 100644 --- a/Shared/Models/FilePath.cs +++ b/Shared/Models/FilePath.cs @@ -26,7 +26,7 @@ public record FilePath(long CreationTicks, return result; } - public static FilePath Get(MetadataConfiguration metadataConfiguration, FileHolder fileHolder, int? index) + public static FilePath Get(ResultSettings resultSettings, MetadataSettings metadataSettings, FileHolder fileHolder, int? index) { if (fileHolder.CreationTime is null) fileHolder = FileHolder.Get(fileHolder); @@ -40,23 +40,23 @@ public record FilePath(long CreationTicks, int? id; int? sortOder; string fileNameFirstSegment = fileHolder.Name.Split('.')[0]; - int sortOrderOnlyLengthIndex = metadataConfiguration.Offset.ToString().Length; + int sortOrderOnlyLengthIndex = metadataSettings.Offset.ToString().Length; string fileDirectoryFullPath = fileHolder.DirectoryFullPath ?? throw new NullReferenceException(); - bool isIntelligentIdFormat = IId.NameWithoutExtensionIsIntelligentIdFormat(metadataConfiguration, fileNameFirstSegment); - bool isPaddedIntelligentIdFormat = IId.NameWithoutExtensionIsPaddedIntelligentIdFormat(metadataConfiguration, sortOrderOnlyLengthIndex, fileNameFirstSegment); - bool fileNameFirstSegmentIsIdFormat = !isPaddedIntelligentIdFormat && !isIntelligentIdFormat && IId.NameWithoutExtensionIsIdFormat(metadataConfiguration, fileHolder); + bool isIntelligentIdFormat = IId.NameWithoutExtensionIsIntelligentIdFormat(metadataSettings, fileNameFirstSegment); + bool isPaddedIntelligentIdFormat = IId.NameWithoutExtensionIsPaddedIntelligentIdFormat(metadataSettings, sortOrderOnlyLengthIndex, fileNameFirstSegment); + bool fileNameFirstSegmentIsIdFormat = !isPaddedIntelligentIdFormat && !isIntelligentIdFormat && IId.NameWithoutExtensionIsIdFormat(metadataSettings, fileHolder); bool? hasIgnoreKeyword = !isIntelligentIdFormat && !isPaddedIntelligentIdFormat ? null : fileNameFirstSegment[^1] is '2' or '8'; bool? hasDateTimeOriginal = !isIntelligentIdFormat && !isPaddedIntelligentIdFormat ? null : fileNameFirstSegment[^1] is '1' or '9'; if (!fileNameFirstSegmentIsIdFormat && !isIntelligentIdFormat && !isPaddedIntelligentIdFormat) (id, sortOder) = (null, null); else if (isIntelligentIdFormat) - (id, sortOder) = (IId.GetId(metadataConfiguration, fileNameFirstSegment), null); + (id, sortOder) = (IId.GetId(resultSettings, metadataSettings, fileNameFirstSegment), null); else if (isPaddedIntelligentIdFormat) { if (!int.TryParse(fileNameFirstSegment[..sortOrderOnlyLengthIndex], out int absoluteValueOfSortOrder)) (id, sortOder) = (null, null); else - (id, sortOder) = (IId.GetId(metadataConfiguration, fileNameFirstSegment[sortOrderOnlyLengthIndex..]), absoluteValueOfSortOrder); + (id, sortOder) = (IId.GetId(resultSettings, metadataSettings, fileNameFirstSegment[sortOrderOnlyLengthIndex..]), absoluteValueOfSortOrder); } else if (fileNameFirstSegmentIsIdFormat) { @@ -64,7 +64,7 @@ public record FilePath(long CreationTicks, throw new NullReferenceException(nameof(index)); if (!int.TryParse(fileNameFirstSegment, out int valueOfFileNameFirstSegment)) throw new NotSupportedException(); - (id, sortOder) = (valueOfFileNameFirstSegment, metadataConfiguration.Offset + index); + (id, sortOder) = (valueOfFileNameFirstSegment, metadataSettings.Offset + index); } else throw new NotSupportedException(); diff --git a/Shared/Models/FilePathAndWholePercentages.cs b/Shared/Models/FilePathAndWholePercentages.cs new file mode 100644 index 0000000..3a8702b --- /dev/null +++ b/Shared/Models/FilePathAndWholePercentages.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record FilePathAndWholePercentages(FilePath FilePath, int WholePercentages) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, FilePathAndWholePercentagesSourceGenerationContext.Default.FilePathAndWholePercentages); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(FilePathAndWholePercentages))] +public partial class FilePathAndWholePercentagesSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/Location.cs b/Shared/Models/Location.cs index d94c7e0..16cc716 100644 --- a/Shared/Models/Location.cs +++ b/Shared/Models/Location.cs @@ -39,4 +39,11 @@ public class Location(int bottom, double confidence, int left, int right, int to public static bool operator !=(Location location1, Location location2) => !(location1 == location2); + public static Location Get(int bottom, double confidence, int height, int left, int right, int top, int width, int zCount) + { + Location result = new(bottom, confidence, left, right, top); + _ = Stateless.Methods.Location.Check(bottom, height, left, right, top, width, zCount, throwException: true); + return result; + } + } \ No newline at end of file diff --git a/Shared/Models/LocationContainer.cs b/Shared/Models/LocationContainer.cs new file mode 100644 index 0000000..583c85d --- /dev/null +++ b/Shared/Models/LocationContainer.cs @@ -0,0 +1,49 @@ +using System.Drawing; + +namespace View_by_Distance.Shared.Models; + +public record LocationContainer(DateOnly? CreationDateOnly, + ExifDirectory? ExifDirectory, + object? Encoding, + FaceFile? FaceFile, + FilePath FilePath, + int? LengthPermyriad, + FilePath? LengthSource, + PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, + RectangleF? Rectangle, + int? WholePercentages) +{ + + public static LocationContainer Get(LocationContainer locationContainer, object? encoding, bool keepExifDirectory) + { + LocationContainer result; + result = new(locationContainer.CreationDateOnly, + keepExifDirectory ? locationContainer.ExifDirectory : null, + encoding, + locationContainer.FaceFile, + locationContainer.FilePath, + locationContainer.LengthPermyriad, + locationContainer.LengthSource, + locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, + locationContainer.Rectangle, + locationContainer.WholePercentages); + return result; + } + + public static LocationContainer Get(LocationContainer source, LocationContainer locationContainer, int lengthPermyriad, bool keepExifDirectory, bool keepEncoding) + { + LocationContainer result; + result = new(locationContainer.CreationDateOnly, + keepExifDirectory ? locationContainer.ExifDirectory : null, + keepEncoding ? locationContainer.Encoding : null, + locationContainer.FaceFile, + locationContainer.FilePath, + lengthPermyriad, + source.FilePath, + locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, + locationContainer.Rectangle, + locationContainer.WholePercentages); + return result; + } + +} \ No newline at end of file diff --git a/Shared/Models/MappingFromFilterPost.cs b/Shared/Models/MappingFromFilterPost.cs new file mode 100644 index 0000000..214ca8b --- /dev/null +++ b/Shared/Models/MappingFromFilterPost.cs @@ -0,0 +1,23 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record MappingFromFilterPost(bool? CanReMap, + bool? InSkipCollection, + bool? IsFocusPerson) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, MappingFromFilterPostGenerationContext.Default.MappingFromFilterPost); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(MappingFromFilterPost))] +public partial class MappingFromFilterPostGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/MetadataConfiguration.cs b/Shared/Models/MetadataConfiguration.cs deleted file mode 100644 index 1896087..0000000 --- a/Shared/Models/MetadataConfiguration.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace View_by_Distance.Shared.Models; - -public record MetadataConfiguration(ResultConfiguration ResultConfiguration, - bool ForceMetadataLastWriteTimeToCreationTime, - string[] IgnoreRulesKeyWords, - int IntMinValueLength, - int Offset, - bool PropertiesChangedForMetadata) -{ - - public override string ToString() - { - string result = JsonSerializer.Serialize(this, MetadataConfigurationSourceGenerationContext.Default.MetadataConfiguration); - return result; - } - -} - -[JsonSourceGenerationOptions(WriteIndented = true)] -[JsonSerializable(typeof(MetadataConfiguration))] -internal partial class MetadataConfigurationSourceGenerationContext : JsonSerializerContext -{ -} \ No newline at end of file diff --git a/Shared/Models/MetadataGroup.cs b/Shared/Models/MetadataGroup.cs new file mode 100644 index 0000000..ddf9a89 --- /dev/null +++ b/Shared/Models/MetadataGroup.cs @@ -0,0 +1,22 @@ +using System.Collections.ObjectModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record MetadataGroup(bool FastForwardMovingPictureExpertsGroupUsed, FilePath FilePath, FileInfo FileInfo, ExifDirectory ExifDirectory, ReadOnlyCollection SidecarFiles) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, MetadataGroupSourceGenerationContext.Default.MetadataGroup); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(MetadataGroup))] +internal partial class MetadataGroupSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/MetadataSettings.cs b/Shared/Models/MetadataSettings.cs new file mode 100644 index 0000000..3c0de05 --- /dev/null +++ b/Shared/Models/MetadataSettings.cs @@ -0,0 +1,25 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record MetadataSettings(bool ForceMetadataLastWriteTimeToCreationTime, + string[] IgnoreRulesKeyWords, + int IntMinValueLength, + int Offset, + bool PropertiesChangedForMetadata) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, MetadataSettingsSourceGenerationContext.Default.MetadataSettings); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(MetadataSettings))] +internal partial class MetadataSettingsSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/PeopleSettings.cs b/Shared/Models/PeopleSettings.cs new file mode 100644 index 0000000..f06a232 --- /dev/null +++ b/Shared/Models/PeopleSettings.cs @@ -0,0 +1,24 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record PeopleSettings(string GenealogicalDataCommunicationFile, + string[] JLinks, + string PersonBirthdayFormat, + string PersonCharacters) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, PeopleSettingsSourceGenerationContext.Default.PeopleSettings); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(PeopleSettings))] +internal partial class PeopleSettingsSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/PersonBirthday.cs b/Shared/Models/PersonBirthday.cs new file mode 100644 index 0000000..1b5b053 --- /dev/null +++ b/Shared/Models/PersonBirthday.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record PersonBirthday(DateTime Value) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, PersonBirthdaySourceGenerationContext.Default.PersonBirthday); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(PersonBirthday))] +public partial class PersonBirthdaySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/PersonContainer.cs b/Shared/Models/PersonContainer.cs new file mode 100644 index 0000000..6dc4526 --- /dev/null +++ b/Shared/Models/PersonContainer.cs @@ -0,0 +1,48 @@ +using System.Collections.ObjectModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record PersonContainer(int? ApproximateYears, + PersonBirthday[]? Birthdays, + ReadOnlyCollection DisplayDirectoryAllFilePaths, + string DisplayDirectoryName, + long? Key, + PersonDirectory? PersonDirectory) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, PersonContainerSourceGenerationContext.Default.PersonContainer); + return result; + } + + public static PersonContainer Get(int? approximateYears, PersonBirthday[]? birthdays, ReadOnlyCollection displayDirectoryAllFilePaths, string displayDirectoryName, long? key, PersonDirectory? personDirectory) => + new(approximateYears, birthdays, displayDirectoryAllFilePaths, displayDirectoryName, key, personDirectory); + + public static PersonContainer Get(char[] personCharacters, PersonBirthday birthday, string displayDirectoryName, PersonDirectory personDirectory) => + new(Stateless.Methods.IAge.GetApproximateYears(personCharacters, displayDirectoryName), [birthday], new([]), displayDirectoryName, birthday.Value.Ticks, personDirectory); + + public static PersonContainer Get(int? approximateYears, PersonBirthday birthdays, string displayDirectoryName, long key) => + new(approximateYears, [birthdays], new([]), displayDirectoryName, key, null); + + public static PersonContainer Get(int? approximateYears, PersonBirthday birthdays, PersonDirectory? personDirectory, string displayDirectoryName, long key) => + new(approximateYears, [birthdays], new([]), displayDirectoryName, key, personDirectory); + + public static PersonContainer Get(int? approximateYears, ReadOnlyCollection displayDirectoryAllFilePaths, string displayDirectoryName, PersonDirectory? personDirectory) => + new(approximateYears, null, displayDirectoryAllFilePaths, displayDirectoryName, null, personDirectory); + + public static PersonContainer Get(int? approximateYears, PersonBirthday[]? birthdays, ReadOnlyCollection displayDirectoryAllFilePaths, string displayDirectoryName, long? key) => + new(approximateYears, birthdays, displayDirectoryAllFilePaths, displayDirectoryName, key, null); + + public static bool? IsKeyIsMaxBirthday(PersonContainer personContainer) => + personContainer.Birthdays is null || personContainer.Key is null ? null : personContainer.Key.Value == personContainer.Birthdays.First().Value.Ticks; + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(PersonContainer))] +public partial class PersonContainerSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/PersonDirectory.cs b/Shared/Models/PersonDirectory.cs new file mode 100644 index 0000000..bb5838c --- /dev/null +++ b/Shared/Models/PersonDirectory.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record PersonDirectory(char Char, string Group, char Status, char Sex, char First) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, PersonDirectorySourceGenerationContext.Default.PersonDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(PersonDirectory))] +public partial class PersonDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/PersonKeyFormattedAndPersonBirthday.cs b/Shared/Models/PersonKeyFormattedAndPersonBirthday.cs new file mode 100644 index 0000000..bd49d5e --- /dev/null +++ b/Shared/Models/PersonKeyFormattedAndPersonBirthday.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record PersonKeyFormattedAndPersonBirthday(string PersonKeyFormatted, PersonBirthday PersonBirthday) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, PersonKeyFormattedAndPersonBirthdaySourceGenerationContext.Default.PersonKeyFormattedAndPersonBirthday); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(PersonKeyFormattedAndPersonBirthday))] +public partial class PersonKeyFormattedAndPersonBirthdaySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/PersonKeyFormattedAndPersonKeyTicks.cs b/Shared/Models/PersonKeyFormattedAndPersonKeyTicks.cs new file mode 100644 index 0000000..32ad454 --- /dev/null +++ b/Shared/Models/PersonKeyFormattedAndPersonKeyTicks.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName(string KeyFormatted, long KeyTicks, string? DisplayDirectoryName) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, PersonKeyFormattedAndKeyTicksAndDisplayDirectoryNameSourceGenerationContext.Default.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName))] +public partial class PersonKeyFormattedAndKeyTicksAndDisplayDirectoryNameSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/Properties/ICompareSettings.cs b/Shared/Models/Properties/ICompareSettings.cs new file mode 100644 index 0000000..b574a13 --- /dev/null +++ b/Shared/Models/Properties/ICompareSettings.cs @@ -0,0 +1,14 @@ +namespace View_by_Distance.Shared.Models.Properties; + +public interface ICompareSettings +{ + + public string FacesFileNameExtension { init; get; } + public string FacesHiddenFileNameExtension { init; get; } + public string FacesPartsFileNameExtension { init; get; } + public string[] IgnoreExtensions { init; get; } + public int MaxDegreeOfParallelism { init; get; } + public string[] ValidImageFormatExtensions { init; get; } + public string[] ValidVideoFormatExtensions { init; get; } + +} \ No newline at end of file diff --git a/Shared/Models/Properties/IFaceDistance.cs b/Shared/Models/Properties/IFaceDistance.cs new file mode 100644 index 0000000..0ce82d9 --- /dev/null +++ b/Shared/Models/Properties/IFaceDistance.cs @@ -0,0 +1,15 @@ +namespace View_by_Distance.Shared.Models.Properties; + +public interface IFaceDistance +{ + + public int? ConfidencePercent { init; get; } + public DateTime DateTimeOriginalThenMinimumDateTime { init; get; } + public object? Encoding { init; get; } + public MappingFromFilterPost? MappingFromFilterPost { init; get; } + public int? Id { init; get; } + public bool? IsWrongYear { init; get; } + public double? Length { init; get; } + public int? WholePercentages { init; get; } + +} \ No newline at end of file diff --git a/Shared/Models/Properties/IRenameConfiguration.cs b/Shared/Models/Properties/IRenameSettings.cs similarity index 72% rename from Shared/Models/Properties/IRenameConfiguration.cs rename to Shared/Models/Properties/IRenameSettings.cs index cc69424..db8a21d 100644 --- a/Shared/Models/Properties/IRenameConfiguration.cs +++ b/Shared/Models/Properties/IRenameSettings.cs @@ -1,9 +1,8 @@ namespace View_by_Distance.Shared.Models.Properties; -public interface IRenameConfiguration +public interface IRenameSettings { - public MetadataConfiguration MetadataConfiguration { init; get; } public string[] IgnoreExtensions { init; get; } public bool SkipIdFiles { init; get; } public string[] ValidImageFormatExtensions { init; get; } diff --git a/Shared/Models/ReadOnlyCollections.cs b/Shared/Models/ReadOnlyCollections.cs new file mode 100644 index 0000000..92f0661 --- /dev/null +++ b/Shared/Models/ReadOnlyCollections.cs @@ -0,0 +1,29 @@ +using System.Collections.ObjectModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record ReadOnlyCollections(ReadOnlyCollection JLinkResolvedPersonKeys, + ReadOnlyCollection PersonContainers, + ReadOnlyCollection PersonKeyFormattedCollection, + ReadOnlyDictionary PersonKeyFormattedToNewestPersonKeyFormatted, + ReadOnlyCollection PersonKeys, + ReadOnlyDictionary> SkipCollection, + ReadOnlyDictionary> SkipNotSkipCollection, + ReadOnlyCollection SkipNotSkipDirectories) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, ReadOnlyCollectionsSourceGenerationContext.Default.ReadOnlyCollections); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(ReadOnlyCollections))] +public partial class ReadOnlyCollectionsSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/ResultConfiguration.cs b/Shared/Models/ResultConfiguration.cs deleted file mode 100644 index 2864135..0000000 --- a/Shared/Models/ResultConfiguration.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace View_by_Distance.Shared.Models; - -public record ResultConfiguration(string DateGroup, - int EpicYear, - string? ModelName, - int? NumberOfJitters, - int? NumberOfTimesToUpsample, - string? PredictorModelName, - int ResultAllInOneSubdirectoryLength, - string ResultCollection, - string ResultContent, - string ResultSingleton, - string RootDirectory) -{ - - public override string ToString() - { - string result = JsonSerializer.Serialize(this, ResultConfigurationSourceGenerationContext.Default.ResultConfiguration); - return result; - } - -} - -[JsonSourceGenerationOptions(WriteIndented = true)] -[JsonSerializable(typeof(ResultConfiguration))] -internal partial class ResultConfigurationSourceGenerationContext : JsonSerializerContext -{ -} \ No newline at end of file diff --git a/Shared/Models/ResultSettings.cs b/Shared/Models/ResultSettings.cs new file mode 100644 index 0000000..29baf15 --- /dev/null +++ b/Shared/Models/ResultSettings.cs @@ -0,0 +1,31 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record ResultSettings(string DateGroup, + int EpicYear, + string ModelName, + int NumberOfJitters, + int NumberOfTimesToUpsample, + string PredictorModelName, + int ResultAllInOneSubdirectoryLength, + string ResultCollection, + string ResultContent, + string ResultSingleton, + string RootDirectory) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, ResultSettingsSourceGenerationContext.Default.ResultSettings); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(ResultSettings))] +internal partial class ResultSettingsSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/SaveContainer.cs b/Shared/Models/SaveContainer.cs new file mode 100644 index 0000000..7d00005 --- /dev/null +++ b/Shared/Models/SaveContainer.cs @@ -0,0 +1,45 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record SaveContainer(string CheckFile, + string Directory, + FileHolder? FaceFileHolder, + FileHolder? HiddenFaceFileHolder, + FileHolder? FacePartsFileHolder, + bool MakeAllHidden, + FileHolder? ResizedFileHolder, + string ShortcutFile) +{ + public static SaveContainer Get(string directory) => + new(string.Empty, directory, null, null, null, false, null, string.Empty); + + public static SaveContainer Get(string directory, string locationContainersFile) => + new(string.Empty, directory, null, null, null, false, FileHolder.Get(locationContainersFile), Path.Combine(directory, $"{Path.GetFileName(locationContainersFile)}.lnk")); + + public static SaveContainer Get(string directory, FileHolder? faceFileHolder, FileHolder? resizedFileHolder, string shortcutFile) => + new(string.Empty, directory, faceFileHolder, null, null, true, resizedFileHolder, shortcutFile); + + public static SaveContainer Get(string checkFile, string directory, FileHolder faceFileHolder) => + new(checkFile, directory, faceFileHolder, null, null, false, null, string.Empty); + + public static SaveContainer Get(FileHolder resizedFileHolder, string checkFile, string directory) => + new(checkFile, directory, null, null, null, false, resizedFileHolder, string.Empty); + + public static SaveContainer Get(string checkFile, string directory, FileHolder? faceFileHolder, FileHolder? hiddenFaceFileHolder, FileHolder? facePartsFileHolder, FileHolder? resizedFileHolder, string shortcutFile) => + new(checkFile, directory, faceFileHolder, hiddenFaceFileHolder, facePartsFileHolder, false, resizedFileHolder, shortcutFile); + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, ResultSettingsSourceGenerationContext.Default.ResultSettings); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(SaveContainer))] +internal partial class SaveContainerSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/Stateless/IMapLogic.cs b/Shared/Models/Stateless/IMapLogic.cs new file mode 100644 index 0000000..7470743 --- /dev/null +++ b/Shared/Models/Stateless/IMapLogic.cs @@ -0,0 +1,14 @@ +namespace View_by_Distance.Shared.Models.Stateless; + +public interface IMapLogic +{ // ... + + const int CopyNotMappedFaces = 5; + const int ForceSingleImage = 3; + const int Individually = 6; + const int ManualCopy = 4; + const int Mapping = 1; + const int Sigma = 3; + const int Sorting = 2; + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Id.cs b/Shared/Models/Stateless/Id.cs index 56b56ec..499d45a 100644 --- a/Shared/Models/Stateless/Id.cs +++ b/Shared/Models/Stateless/Id.cs @@ -6,10 +6,10 @@ namespace View_by_Distance.Shared.Models.Stateless; internal abstract class Id { - internal static bool NameWithoutExtensionIsIdFormat(MetadataConfiguration metadataConfiguration, string fileNameFirstSegment) + internal static bool NameWithoutExtensionIsIdFormat(MetadataSettings metadataSettings, string fileNameFirstSegment) { bool result; - if (fileNameFirstSegment.Length < 5 || fileNameFirstSegment.Length > metadataConfiguration.IntMinValueLength) + if (fileNameFirstSegment.Length < 5 || fileNameFirstSegment.Length > metadataSettings.IntMinValueLength) result = false; else { @@ -19,13 +19,13 @@ internal abstract class Id return result; } - internal static int GetId(MetadataConfiguration metadataConfiguration, string intelligentId) + internal static int GetId(ResultSettings resultSettings, MetadataSettings metadataSettings, string intelligentId) { int result; StringBuilder results = new(); - if (metadataConfiguration.IntMinValueLength < (metadataConfiguration.ResultConfiguration.ResultAllInOneSubdirectoryLength + 2)) + if (metadataSettings.IntMinValueLength < (resultSettings.ResultAllInOneSubdirectoryLength + 2)) throw new NotSupportedException(); - for (int i = intelligentId.Length - (metadataConfiguration.ResultConfiguration.ResultAllInOneSubdirectoryLength + 2); i > -1; i--) + for (int i = intelligentId.Length - (resultSettings.ResultAllInOneSubdirectoryLength + 2); i > -1; i--) _ = results.Append(intelligentId[i]); _ = results.Append(intelligentId[^3]).Append(intelligentId[^2]); result = int.Parse(results.ToString()); @@ -37,12 +37,12 @@ internal abstract class Id } #pragma warning disable IDE0060 - internal static string GetIntelligentId(MetadataConfiguration metadataConfiguration, long id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal) + internal static string GetIntelligentId(ResultSettings resultSettings, MetadataSettings metadataSettings, long id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal) #pragma warning restore IDE0060 { string result; StringBuilder stringBuilder = new(); - if (metadataConfiguration.IntMinValueLength < (metadataConfiguration.ResultConfiguration.ResultAllInOneSubdirectoryLength + 2)) + if (metadataSettings.IntMinValueLength < (resultSettings.ResultAllInOneSubdirectoryLength + 2)) throw new NotSupportedException(); int key; string value; @@ -50,33 +50,33 @@ internal abstract class Id if (id > -1) { key = hasIgnoreKeyword is not null && hasIgnoreKeyword.Value ? 8 : 9; - value = id.ToString().PadLeft(metadataConfiguration.IntMinValueLength, '0'); + value = id.ToString().PadLeft(metadataSettings.IntMinValueLength, '0'); } else { key = hasIgnoreKeyword is not null && hasIgnoreKeyword.Value ? 2 : 1; - value = id.ToString()[1..].PadLeft(metadataConfiguration.IntMinValueLength, '0'); + value = id.ToString()[1..].PadLeft(metadataSettings.IntMinValueLength, '0'); } - for (int i = value.Length - metadataConfiguration.ResultConfiguration.ResultAllInOneSubdirectoryLength - 1; i > -1; i--) + for (int i = value.Length - resultSettings.ResultAllInOneSubdirectoryLength - 1; i > -1; i--) _ = stringBuilder.Append(value[i]); - for (int i = value.Length - metadataConfiguration.ResultConfiguration.ResultAllInOneSubdirectoryLength; i < value.Length; i++) + for (int i = value.Length - resultSettings.ResultAllInOneSubdirectoryLength; i < value.Length; i++) resultAllInOneSubdirectoryChars.Add(value[i]); result = $"{stringBuilder}{string.Join(string.Empty, resultAllInOneSubdirectoryChars)}{key}"; return result; } - internal static string GetPaddedId(MetadataConfiguration metadataConfiguration, int id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal, int? index) + internal static string GetPaddedId(ResultSettings resultSettings, MetadataSettings metadataSettings, int id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal, int? index) { string result; - if (metadataConfiguration.Offset < 0) + if (metadataSettings.Offset < 0) result = Guid.NewGuid().ToString(); else { - string intelligentId = GetIntelligentId(metadataConfiguration, id, hasIgnoreKeyword, hasDateTimeOriginal); - int check = GetId(metadataConfiguration, intelligentId); + string intelligentId = GetIntelligentId(resultSettings, metadataSettings, id, hasIgnoreKeyword, hasDateTimeOriginal); + int check = GetId(resultSettings, metadataSettings, intelligentId); if (check != id) throw new NotSupportedException(); - result = index is null || metadataConfiguration.Offset == IId.DeterministicHashCode ? intelligentId : $"{metadataConfiguration.Offset + index}{intelligentId}"; + result = index is null || metadataSettings.Offset == IId.DeterministicHashCode ? intelligentId : $"{metadataSettings.Offset + index}{intelligentId}"; } return result; } diff --git a/Shared/Models/Stateless/ImageFormat.cs b/Shared/Models/Stateless/ImageFormat.cs new file mode 100644 index 0000000..b1024a7 --- /dev/null +++ b/Shared/Models/Stateless/ImageFormat.cs @@ -0,0 +1,24 @@ +namespace View_by_Distance.Shared.Models.Stateless; + +/// +/// Specifies the file format of the image. +/// +public enum ImageFormat +{ + + /// + /// Specifies that the bitmap (BMP) image format. + /// + Bmp, + + /// + /// Specifies that the Joint Photographic Experts Group (JPEG) image format. + /// + Jpeg, + + /// + /// Specifies that the W3C Portable Network Graphics (PNG) image format. + /// + Png, + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/Age.cs b/Shared/Models/Stateless/Methods/Age.cs new file mode 100644 index 0000000..869ab18 --- /dev/null +++ b/Shared/Models/Stateless/Methods/Age.cs @@ -0,0 +1,46 @@ +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +internal abstract class Age +{ + + internal static (int, TimeSpan) GetAge(long minuendTicks, long subtrahendTicks) + { + TimeSpan result; + int years = 0; + DateTime check = new(subtrahendTicks); + for (int i = 0; i < int.MaxValue; i++) + { + check = check.AddYears(1); + if (check.Ticks > minuendTicks) + break; + years += 1; + } + result = new(minuendTicks - check.AddYears(-1).Ticks); + return (years, result); + } + + internal static (int, TimeSpan) GetAge(long minuendTicks, DateTime subtrahend) + { + (int years, TimeSpan result) = GetAge(minuendTicks, subtrahend.Ticks); + return (years, result); + } + + internal static (int, TimeSpan) GetAge(DateTime minuend, DateTime subtrahend) + { + (int years, TimeSpan result) = GetAge(minuend.Ticks, subtrahend.Ticks); + return (years, result); + } + + internal static int? GetApproximateYears(char[] personCharacters, string personDisplayDirectoryName) + { + int? result; + const int zero = 0; + string[] segments = personDisplayDirectoryName.Split(personCharacters); + if (segments.Length == 1 || !int.TryParse(segments[1].Split('-')[zero], out int years)) + result = null; + else + result = years; + return result; + } + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IAge.cs b/Shared/Models/Stateless/Methods/IAge.cs new file mode 100644 index 0000000..c022bd9 --- /dev/null +++ b/Shared/Models/Stateless/Methods/IAge.cs @@ -0,0 +1,26 @@ +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface IAge +{ // ... + + (int, TimeSpan) TestStatic_GetAge(long minuendTicks, long subtrahendTicks) => + GetAge(minuendTicks, subtrahendTicks); + static (int, TimeSpan) GetAge(long minuendTicks, long subtrahendTicks) => + Age.GetAge(minuendTicks, subtrahendTicks); + + (int, TimeSpan) TestStatic_GetAge(long minuendTicks, DateTime subtrahend) => + GetAge(minuendTicks, subtrahend); + static (int, TimeSpan) GetAge(long minuendTicks, DateTime subtrahend) => + Age.GetAge(minuendTicks, subtrahend); + + (int, TimeSpan) TestStatic_GetAge(DateTime minuend, DateTime subtrahend) => + GetAge(minuend, subtrahend); + static (int, TimeSpan) GetAge(DateTime minuend, DateTime subtrahend) => + Age.GetAge(minuend, subtrahend); + + int? TestStatic_GetApproximateYears(char[] personCharacters, string personDisplayDirectoryName) => + GetApproximateYears(personCharacters, personDisplayDirectoryName); + static int? GetApproximateYears(char[] personCharacters, string personDisplayDirectoryName) => + Age.GetApproximateYears(personCharacters, personDisplayDirectoryName); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/ICompare.cs b/Shared/Models/Stateless/Methods/ICompare.cs new file mode 100644 index 0000000..59fd4f3 --- /dev/null +++ b/Shared/Models/Stateless/Methods/ICompare.cs @@ -0,0 +1,9 @@ +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface ICompare +{ + + void Tick(); + void ConstructProgressBar(int maxTicks, string message); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IId.cs b/Shared/Models/Stateless/Methods/IId.cs index 61375eb..4780def 100644 --- a/Shared/Models/Stateless/Methods/IId.cs +++ b/Shared/Models/Stateless/Methods/IId.cs @@ -5,23 +5,23 @@ public interface IId const int DeterministicHashCode = 9876543; - static bool IsOffsetDeterministicHashCode(MetadataConfiguration metadataConfiguration) => - metadataConfiguration.Offset == DeterministicHashCode; + static bool IsOffsetDeterministicHashCode(MetadataSettings metadataSettings) => + metadataSettings.Offset == DeterministicHashCode; - string TestStatic_GetIntelligentId(MetadataConfiguration metadataConfiguration, long id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal) => - GetIntelligentId(metadataConfiguration, id, hasIgnoreKeyword, hasDateTimeOriginal); - static string GetIntelligentId(MetadataConfiguration metadataConfiguration, long id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal) => - Id.GetIntelligentId(metadataConfiguration, id, hasIgnoreKeyword, hasDateTimeOriginal); + string TestStatic_GetIntelligentId(ResultSettings resultSettings, MetadataSettings metadataSettings, long id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal) => + GetIntelligentId(resultSettings, metadataSettings, id, hasIgnoreKeyword, hasDateTimeOriginal); + static string GetIntelligentId(ResultSettings resultSettings, MetadataSettings metadataSettings, long id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal) => + Id.GetIntelligentId(resultSettings, metadataSettings, id, hasIgnoreKeyword, hasDateTimeOriginal); - int TestStatic_GetId(MetadataConfiguration metadataConfiguration, string intelligentId) => - GetId(metadataConfiguration, intelligentId); - static int GetId(MetadataConfiguration metadataConfiguration, string intelligentId) => - Id.GetId(metadataConfiguration, intelligentId); + int TestStatic_GetId(ResultSettings resultSettings, MetadataSettings metadataSettings, string intelligentId) => + GetId(resultSettings, metadataSettings, intelligentId); + static int GetId(ResultSettings resultSettings, MetadataSettings metadataSettings, string intelligentId) => + Id.GetId(resultSettings, metadataSettings, intelligentId); - string TestStatic_GetPaddedId(MetadataConfiguration metadataConfiguration, int id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal, int? index) => - GetPaddedId(metadataConfiguration, id, hasIgnoreKeyword, hasDateTimeOriginal, index); - static string GetPaddedId(MetadataConfiguration metadataConfiguration, int id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal, int? index) => - Id.GetPaddedId(metadataConfiguration, id, hasIgnoreKeyword, hasDateTimeOriginal, index); + string TestStatic_GetPaddedId(ResultSettings resultSettings, MetadataSettings metadataSettings, int id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal, int? index) => + GetPaddedId(resultSettings, metadataSettings, id, hasIgnoreKeyword, hasDateTimeOriginal, index); + static string GetPaddedId(ResultSettings resultSettings, MetadataSettings metadataSettings, int id, bool? hasIgnoreKeyword, bool? hasDateTimeOriginal, int? index) => + Id.GetPaddedId(resultSettings, metadataSettings, id, hasIgnoreKeyword, hasDateTimeOriginal, index); string TestStatic_GetIgnoreFullPath(FilePath filePath, FileHolder fileHolder) => GetIgnoreFullPath(filePath, fileHolder); @@ -36,22 +36,22 @@ public interface IId Path.Combine(fileHolder.DirectoryFullPath, $"{fileHolder.NameWithoutExtension[..^1]}2{fileHolder.ExtensionLowered}") : throw new NotSupportedException("Low"); - 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_NameWithoutExtensionIsIntelligentIdFormat(MetadataSettings metadataSettings, string fileNameFirstSegment) => + NameWithoutExtensionIsIntelligentIdFormat(metadataSettings, fileNameFirstSegment); + static bool NameWithoutExtensionIsIntelligentIdFormat(MetadataSettings metadataSettings, string fileNameFirstSegment) => + fileNameFirstSegment.Length - 1 == metadataSettings.IntMinValueLength && fileNameFirstSegment[^1] is '1' or '2' or '8' or '9' && fileNameFirstSegment.All(char.IsNumber); - bool TestStatic_NameWithoutExtensionIsPaddedIntelligentIdFormat(MetadataConfiguration metadataConfiguration, int sortOrderOnlyLengthIndex, string fileNameFirstSegment) => - NameWithoutExtensionIsPaddedIntelligentIdFormat(metadataConfiguration, sortOrderOnlyLengthIndex, fileNameFirstSegment); - static bool NameWithoutExtensionIsPaddedIntelligentIdFormat(MetadataConfiguration metadataConfiguration, int sortOrderOnlyLengthIndex, string fileNameFirstSegment) => - fileNameFirstSegment.Length == metadataConfiguration.IntMinValueLength + sortOrderOnlyLengthIndex + 1 + bool TestStatic_NameWithoutExtensionIsPaddedIntelligentIdFormat(MetadataSettings metadataSettings, int sortOrderOnlyLengthIndex, string fileNameFirstSegment) => + NameWithoutExtensionIsPaddedIntelligentIdFormat(metadataSettings, sortOrderOnlyLengthIndex, fileNameFirstSegment); + static bool NameWithoutExtensionIsPaddedIntelligentIdFormat(MetadataSettings metadataSettings, int sortOrderOnlyLengthIndex, string fileNameFirstSegment) => + fileNameFirstSegment.Length == metadataSettings.IntMinValueLength + sortOrderOnlyLengthIndex + 1 && fileNameFirstSegment[^1] is '1' or '2' or '8' or '9' && fileNameFirstSegment.All(char.IsNumber); - bool TestStatic_NameWithoutExtensionIsIdFormat(MetadataConfiguration metadataConfiguration, FileHolder fileHolder) => - NameWithoutExtensionIsIdFormat(metadataConfiguration, fileHolder); - static bool NameWithoutExtensionIsIdFormat(MetadataConfiguration metadataConfiguration, FileHolder fileHolder) => - Id.NameWithoutExtensionIsIdFormat(metadataConfiguration, fileHolder.NameWithoutExtension.Split('.')[0]); + bool TestStatic_NameWithoutExtensionIsIdFormat(MetadataSettings metadataSettings, FileHolder fileHolder) => + NameWithoutExtensionIsIdFormat(metadataSettings, fileHolder); + static bool NameWithoutExtensionIsIdFormat(MetadataSettings metadataSettings, FileHolder fileHolder) => + Id.NameWithoutExtensionIsIdFormat(metadataSettings, fileHolder.NameWithoutExtension.Split('.')[0]); int TestStatic_GetDeterministicHashCode(byte[] value) => GetDeterministicHashCode(value); diff --git a/Shared/Models/Stateless/Methods/ILocation.cs b/Shared/Models/Stateless/Methods/ILocation.cs new file mode 100644 index 0000000..993d6de --- /dev/null +++ b/Shared/Models/Stateless/Methods/ILocation.cs @@ -0,0 +1,25 @@ +using System.Drawing; + +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface ILocation +{ + + RectangleF? TestStatic_GetPercentagesRectangle(DistanceSettings distanceSettings, int wholePercentages) => + GetPercentagesRectangle(distanceSettings, wholePercentages); + static RectangleF? GetPercentagesRectangle(DistanceSettings distanceSettings, int wholePercentages) => + Location.GetPercentagesRectangle(distanceSettings, wholePercentages); + + Models.Location TestStatic_GetTrimBound(double detectionConfidence, Rectangle rectangle, int width, int height, int facesCount) => + TrimBound(detectionConfidence, rectangle, width, height, facesCount); + static Models.Location TrimBound(double detectionConfidence, Rectangle rectangle, int width, int height, int facesCount) => + Models.Location.Get(Math.Min(rectangle.Bottom, height), + detectionConfidence, + height, + Math.Max(rectangle.Left, 0), + Math.Min(rectangle.Right, width), + Math.Max(rectangle.Top, 0), + width, + facesCount); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IMapping.cs b/Shared/Models/Stateless/Methods/IMapping.cs new file mode 100644 index 0000000..1e909bb --- /dev/null +++ b/Shared/Models/Stateless/Methods/IMapping.cs @@ -0,0 +1,28 @@ +using View_by_Distance.Shared.Models.Properties; + +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface IMapping +{ // ... + + int TestStatic_GetAreaPermyriad(int faceAreaPermyriad, int height, Models.Location location, int width) + => GetAreaPermyriad(faceAreaPermyriad, height, location, width); + static int GetAreaPermyriad(int faceAreaPermyriad, int height, Models.Location location, int width) + => Mapping.GetAreaPermyriad(faceAreaPermyriad, location.Bottom, height, location.Left, location.Right, location.Top, width); + + int TestStatic_GetAreaPermyriad(int faceAreaPermyriad, int bottom, int height, int left, int right, int top, int width) + => GetAreaPermyriad(faceAreaPermyriad, bottom, height, left, right, top, width); + static int GetAreaPermyriad(int faceAreaPermyriad, int bottom, int height, int left, int right, int top, int width) + => Mapping.GetAreaPermyriad(faceAreaPermyriad, bottom, height, left, right, top, width); + + int TestStatic_GetAreaPermyriad(int faceAreaPermyriad, Models.Location location, OutputResolution outputResolution) + => GetAreaPermyriad(faceAreaPermyriad, location, outputResolution); + static int GetAreaPermyriad(int faceAreaPermyriad, Models.Location location, OutputResolution outputResolution) + => Mapping.GetAreaPermyriad(faceAreaPermyriad, location.Bottom, outputResolution.Height, location.Left, location.Right, location.Top, outputResolution.Width); + + int? TestStatic_GetWholePercentages(ICompareSettings compareSettings, FilePath filePath) => + GetWholePercentages(compareSettings, filePath); + static int? GetWholePercentages(ICompareSettings compareSettings, FilePath filePath) => + Mapping.GetWholePercentages(compareSettings, filePath); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IPath.cs b/Shared/Models/Stateless/Methods/IPath.cs index 5b6ca45..e8ebb1f 100644 --- a/Shared/Models/Stateless/Methods/IPath.cs +++ b/Shared/Models/Stateless/Methods/IPath.cs @@ -61,24 +61,24 @@ public interface IPath static string GetDirectory(string sourceDirectory, int level, string directoryName) => XPath.GetDirectory(sourceDirectory, level, directoryName); - (string, int) TestStatic_GetDirectoryNameAndIndex(ResultConfiguration resultConfiguration, FileHolder fileHolder) => - GetDirectoryNameAndIndex(resultConfiguration, fileHolder); - static (string, int) GetDirectoryNameAndIndex(ResultConfiguration resultConfiguration, FileHolder fileHolder) => - XPath.GetDirectoryNameAndIndex(resultConfiguration, fileHolder); + (string, int) TestStatic_GetDirectoryNameAndIndex(ResultSettings resultSettings, FileHolder fileHolder) => + GetDirectoryNameAndIndex(resultSettings, fileHolder); + static (string, int) GetDirectoryNameAndIndex(ResultSettings resultSettings, FileHolder fileHolder) => + XPath.GetDirectoryNameAndIndex(resultSettings, fileHolder); - (string, int) TestStatic_GetDirectoryNameAndIndex(ResultConfiguration resultConfiguration, FilePath filePath) => - GetDirectoryNameAndIndex(resultConfiguration, filePath); - static (string, int) GetDirectoryNameAndIndex(ResultConfiguration resultConfiguration, FilePath filePath) => - XPath.GetDirectoryNameAndIndex(resultConfiguration, filePath); + (string, int) TestStatic_GetDirectoryNameAndIndex(ResultSettings resultSettings, FilePath filePath) => + GetDirectoryNameAndIndex(resultSettings, filePath); + static (string, int) GetDirectoryNameAndIndex(ResultSettings resultSettings, FilePath filePath) => + XPath.GetDirectoryNameAndIndex(resultSettings, filePath); - (string, int) TestStatic_GetDirectoryNameAndIndex(ResultConfiguration resultConfiguration, int id) => - GetDirectoryNameAndIndex(resultConfiguration, id); - static (string, int) GetDirectoryNameAndIndex(ResultConfiguration resultConfiguration, int id) => - XPath.GetDirectoryNameAndIndex(resultConfiguration, id); + (string, int) TestStatic_GetDirectoryNameAndIndex(ResultSettings resultSettings, int id) => + GetDirectoryNameAndIndex(resultSettings, id); + static (string, int) GetDirectoryNameAndIndex(ResultSettings resultSettings, int id) => + XPath.GetDirectoryNameAndIndex(resultSettings, id); - ReadOnlyDictionary> TestStatic_GetKeyValuePairs(ResultConfiguration resultConfiguration, string? resultsFullGroupDirectory, string[]? jsonGroups) => - GetKeyValuePairs(resultConfiguration, resultsFullGroupDirectory, jsonGroups); - static ReadOnlyDictionary> GetKeyValuePairs(ResultConfiguration resultConfiguration, string? resultsFullGroupDirectory, string[]? jsonGroups) => - XPath.GetKeyValuePairs(resultConfiguration, resultsFullGroupDirectory, jsonGroups); + ReadOnlyDictionary> TestStatic_GetKeyValuePairs(ResultSettings resultSettings, string? resultsFullGroupDirectory, string[]? jsonGroups) => + GetKeyValuePairs(resultSettings, resultsFullGroupDirectory, jsonGroups); + static ReadOnlyDictionary> GetKeyValuePairs(ResultSettings resultSettings, string? resultsFullGroupDirectory, string[]? jsonGroups) => + XPath.GetKeyValuePairs(resultSettings, resultsFullGroupDirectory, jsonGroups); } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IPerson.cs b/Shared/Models/Stateless/Methods/IPerson.cs new file mode 100644 index 0000000..7e26d31 --- /dev/null +++ b/Shared/Models/Stateless/Methods/IPerson.cs @@ -0,0 +1,55 @@ +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface IPerson +{ + + // ... + + static (char, char, char) GetPersonHour(string? personDisplayDirectoryName, int hour) => + hour == 0 ? new('U', 'U', 'U') : + hour == 1 ? new('U', 'U', 'U') : + hour == 2 ? new('U', 'U', 'U') : + hour == 3 ? new('A', 'U', 'Y') : + hour == 4 ? new('A', 'F', 'Y') : + hour == 5 ? new('A', 'M', 'Y') : + hour == 6 ? new('A', 'F', 'N') : + hour == 7 ? new('A', 'M', 'N') : + hour == 13 ? new('D', 'U', 'Y') : + hour == 14 ? new('D', 'F', 'Y') : + hour == 15 ? new('D', 'M', 'Y') : + hour == 16 ? new('D', 'F', 'N') : + hour == 17 ? new('D', 'M', 'N') : + throw new NotImplementedException(personDisplayDirectoryName); + + static string GetHourGroup(string? personDisplayDirectoryName, int hour) => + hour == 0 ? "Unknown-Unknown-Unknown" : + hour == 1 ? "Unknown-Unknown-Unknown" : + hour == 2 ? "Unknown-Unknown-Unknown" : + hour == 3 ? "Alive-Unknown-Yes" : + hour == 4 ? "Alive-Female-Yes" : + hour == 5 ? "Alive-Male-Yes" : + hour == 6 ? "Alive-Female-No" : + hour == 7 ? "Alive-Male-No" : + hour == 13 ? "Dead-Unknown-Yes" : + hour == 14 ? "Dead-Female-Yes" : + hour == 15 ? "Dead-Male-Yes" : + hour == 16 ? "Dead-Female-No" : + hour == 17 ? "Dead-Male-No" : + throw new NotImplementedException(personDisplayDirectoryName); + + bool TestStatic_IsDefaultName(string personDisplayDirectoryName) => + IsDefaultName(personDisplayDirectoryName); + static bool IsDefaultName(string personDisplayDirectoryName) => + personDisplayDirectoryName.Length > 1 && personDisplayDirectoryName[0] == 'X' && personDisplayDirectoryName[1] == '+'; + + bool TestStatic_IsDefaultName(Models.PersonContainer personContainer) => + IsDefaultName(personContainer); + static bool IsDefaultName(Models.PersonContainer personContainer) => + personContainer.ApproximateYears is null || IsDefaultName(personContainer.DisplayDirectoryName); + + bool TestStatic_IsDefaultName(MappingFromPerson mappingFromPerson) => + IsDefaultName(mappingFromPerson); + static bool IsDefaultName(MappingFromPerson mappingFromPerson) => + mappingFromPerson.ApproximateYears is null || IsDefaultName(mappingFromPerson.DisplayDirectoryName); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IPersonBirthday.cs b/Shared/Models/Stateless/Methods/IPersonBirthday.cs new file mode 100644 index 0000000..f9401ad --- /dev/null +++ b/Shared/Models/Stateless/Methods/IPersonBirthday.cs @@ -0,0 +1,116 @@ +using System.Globalization; + +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface IPersonBirthday +{ + + DateTime TestStatic_GetDefaultValue() => + GetDefaultValue(); + + static DateTime GetDefaultValue() => + DateTime.MinValue; // {{1}}SingletonValue + + // ... + + static bool IsCounterPersonBirthday(Models.PersonBirthday personBirthday) => + personBirthday.Value.Year < 1809; + + static bool IsCounterPersonYear(long personKey) => + new DateTime(personKey).Year < 1809; + + static bool IsCounterPersonYear(int year) => + year < 1809; + + static bool IsCounterPersonYear(string year) => + new string[] { year, "1809" }.Min() == year; + + static bool IsWrongYearFilterOrCounterPersonBirthday(bool? isWrongYear, Models.PersonBirthday personBirthday) => + isWrongYear is null || isWrongYear.Value || IsCounterPersonBirthday(personBirthday); + + string TestStatic_GetDateTime(string personKeyFormatted) => + GetDateTime(personKeyFormatted); + static string GetDateTime(string personKeyFormatted) => + personKeyFormatted.Length < 5 || !personKeyFormatted.Contains('#') ? personKeyFormatted : personKeyFormatted[..2] == "19" ? $"1600{personKeyFormatted[4..]}" : $"1700{personKeyFormatted[4..]}"; + + double? TestStatic_GetAge(Models.PersonBirthday birthday) => + GetAge(birthday); + static double? GetAge(Models.PersonBirthday birthday) => + PersonBirthday.GetAge(birthday); + + int TestStatic_GetHour(bool alive, char sex) => + GetHour(alive, sex); + static int GetHour(bool alive, char sex) => + alive ? sex is 'M' ? 5 : sex is 'F' ? 4 : sex is 'U' ? 2 : throw new NotImplementedException() : sex is 'M' ? 15 : sex is 'F' ? 14 : sex is 'U' ? 3 : throw new NotImplementedException(); + + int TestStatic_GetHour(bool alive, ConsoleKey consoleKey) => + GetHour(alive, consoleKey); + static int GetHour(bool alive, ConsoleKey consoleKey) => + GetHour(alive, consoleKey.ToString()[0]); + + Models.PersonBirthday TestStatic_GetPersonBirthday(long ticks) => + new(new DateTime(ticks)); + static Models.PersonBirthday GetPersonBirthday(long ticks) => + new(new DateTime(ticks)); + + DateTime? TestStatic_GetDate(string month, string day, string year) => + GetDate(month, day, year); + static DateTime? GetDate(string month, string day, string year) => + PersonBirthday.GetDate(month, day, year); + + string TestStatic_GetFileName(string personBirthdayFormat, Models.PersonBirthday personBirthday) => + GetFileName(personBirthdayFormat, personBirthday); + static string GetFileName(string personBirthdayFormat, Models.PersonBirthday personBirthday) => + $"{personBirthday.Value.ToString(personBirthdayFormat)}.json"; + + (int, TimeSpan) TestStatic_GetAge(DateTime dateTime, Models.PersonBirthday birthday) => + GetAge(dateTime, birthday); + static (int, TimeSpan) GetAge(DateTime dateTime, Models.PersonBirthday birthday) => + PersonBirthday.GetAge(dateTime, birthday); + + (int, TimeSpan) TestStatic_GetAge(long dateTimeTicks, Models.PersonBirthday birthday) => + GetAge(dateTimeTicks, birthday); + static (int, TimeSpan) GetAge(long dateTimeTicks, Models.PersonBirthday birthday) => + PersonBirthday.GetAge(dateTimeTicks, birthday); + + string TestStatic_GetFormatted(string personBirthdayFormat, Models.PersonBirthday personBirthday) => + GetFormatted(personBirthdayFormat, personBirthday); + static string GetFormatted(string personBirthdayFormat, Models.PersonBirthday personBirthday) => + personBirthday.Value.ToString(personBirthdayFormat); + + string TestStatic_GetFormatted(string personBirthdayFormat, long personKey) => + GetFormatted(personBirthdayFormat, personKey); + static string GetFormatted(string personBirthdayFormat, long personKey) => + GetFormatted(personBirthdayFormat, GetPersonBirthday(personKey)); + + TimeSpan? TestStatic_Get(DateTime minimumDateTime, Models.PersonBirthday personBirthday) => + GetTimeSpan(minimumDateTime, isWrongYear: false, personBirthday); + static TimeSpan? GetTimeSpan(DateTime minimumDateTime, Models.PersonBirthday personBirthday) => + PersonBirthday.GetTimeSpan(minimumDateTime, isWrongYear: false, personBirthday); + + Models.PersonBirthday? TestStatic_GetPersonBirthday(string personBirthdayFormat, string personKeyFormatted) => + GetPersonBirthday(personBirthdayFormat, personKeyFormatted); + static Models.PersonBirthday? GetPersonBirthday(string personBirthdayFormat, string personKeyFormatted) => + PersonBirthday.GetPersonBirthday(personBirthdayFormat, personKeyFormatted); + + TimeSpan? TestStatic_Get(long minimumDateTimeTicks, bool? isWrongYear, Models.PersonBirthday personBirthday) => + GetTimeSpan(minimumDateTimeTicks, isWrongYear, personBirthday); + static TimeSpan? GetTimeSpan(long minimumDateTimeTicks, bool? isWrongYear, Models.PersonBirthday personBirthday) => + PersonBirthday.GetTimeSpan(minimumDateTimeTicks, isWrongYear, personBirthday); + + TimeSpan? TestStatic_Get(DateTime minimumDateTime, bool? isWrongYear, Models.PersonBirthday personBirthday) => + GetTimeSpan(minimumDateTime, isWrongYear, personBirthday); + static TimeSpan? GetTimeSpan(DateTime minimumDateTime, bool? isWrongYear, Models.PersonBirthday personBirthday) => + PersonBirthday.GetTimeSpan(minimumDateTime, isWrongYear, personBirthday); + + DateTime? TestStatic_GetDateTime(string personBirthdayFormat, string personKeyFormatted) => + GetDateTime(personBirthdayFormat, personKeyFormatted); + static DateTime? GetDateTime(string personBirthdayFormat, string personKeyFormatted) => + DateTime.TryParseExact(GetDateTime(personKeyFormatted), personBirthdayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime) ? dateTime : null; + + List TestStatic_GetPersonBirthdays(string personBirthdayFormat, string[] personKeyDirectories, string personDisplayDirectoryName) => + GetPersonBirthdays(personBirthdayFormat, personKeyDirectories, personDisplayDirectoryName); + static List GetPersonBirthdays(string personBirthdayFormat, string[] personKeyDirectories, string personDisplayDirectoryName) => + PersonBirthday.GetPersonBirthdays(personBirthdayFormat, personKeyDirectories, personDisplayDirectoryName); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IRename.cs b/Shared/Models/Stateless/Methods/IRename.cs index 22ebd63..a4f2715 100644 --- a/Shared/Models/Stateless/Methods/IRename.cs +++ b/Shared/Models/Stateless/Methods/IRename.cs @@ -6,7 +6,7 @@ namespace View_by_Distance.Shared.Models.Stateless.Methods; public interface IRename { - ReadOnlyCollection ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(IRenameConfiguration renameConfiguration, FilePath filePath); + ReadOnlyCollection ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(IRenameSettings renameSettings, FilePath filePath); DeterministicHashCode GetDeterministicHashCode(FilePath filePath); void Tick(); diff --git a/Shared/Models/Stateless/Methods/IResult.cs b/Shared/Models/Stateless/Methods/IResult.cs index f90abd0..5753c52 100644 --- a/Shared/Models/Stateless/Methods/IResult.cs +++ b/Shared/Models/Stateless/Methods/IResult.cs @@ -3,44 +3,44 @@ namespace View_by_Distance.Shared.Models.Stateless.Methods; public interface IResult { - string TestStatic_GetRelativePath(ResultConfiguration resultConfiguration, string path) => - GetRelativePath(resultConfiguration, path); - static string GetRelativePath(ResultConfiguration resultConfiguration, string path) => - XResult.GetRelativePath(resultConfiguration, path); + string TestStatic_GetRelativePath(ResultSettings resultSettings, string path) => + GetRelativePath(resultSettings, path); + static string GetRelativePath(ResultSettings resultSettings, string path) => + XResult.GetRelativePath(resultSettings, path); - string TestStatic_GetResultsGroupDirectory(ResultConfiguration resultConfiguration, string description, bool create) => - GetResultsGroupDirectory(resultConfiguration, description, create); - static string GetResultsGroupDirectory(ResultConfiguration resultConfiguration, string description, bool create) => - XResult.GetResultsGroupDirectory(resultConfiguration, description, create); + string TestStatic_GetResultsGroupDirectory(ResultSettings resultSettings, string description, bool create) => + GetResultsGroupDirectory(resultSettings, description, create); + static string GetResultsGroupDirectory(ResultSettings resultSettings, string description, bool create) => + XResult.GetResultsGroupDirectory(resultSettings, description, create); - string TestStatic_GetResultsGroupDirectory(ResultConfiguration resultConfiguration, string description) => - GetResultsGroupDirectory(resultConfiguration, description); - static string GetResultsGroupDirectory(ResultConfiguration resultConfiguration, string description) => - XResult.GetResultsGroupDirectory(resultConfiguration, description, create: true); + string TestStatic_GetResultsGroupDirectory(ResultSettings resultSettings, string description) => + GetResultsGroupDirectory(resultSettings, description); + static string GetResultsGroupDirectory(ResultSettings resultSettings, string description) => + XResult.GetResultsGroupDirectory(resultSettings, description, create: true); - string TestStatic_GetResultsDateGroupDirectory(ResultConfiguration resultConfiguration, string description) => - GetResultsDateGroupDirectory(resultConfiguration, description); - static string GetResultsDateGroupDirectory(ResultConfiguration resultConfiguration, string description) => - XResult.GetResultsDateGroupDirectory(resultConfiguration, description); + string TestStatic_GetResultsDateGroupDirectory(ResultSettings resultSettings, string description) => + GetResultsDateGroupDirectory(resultSettings, description); + static string GetResultsDateGroupDirectory(ResultSettings resultSettings, string description) => + XResult.GetResultsDateGroupDirectory(resultSettings, description); - string TestStatic_GetResultsDateGroupDirectory(ResultConfiguration resultConfiguration, string description, string jsonGroup) => - GetResultsDateGroupDirectory(resultConfiguration, description, jsonGroup); - static string GetResultsDateGroupDirectory(ResultConfiguration resultConfiguration, string description, string jsonGroup) => - XResult.GetResultsDateGroupDirectory(resultConfiguration, description, jsonGroup); + string TestStatic_GetResultsDateGroupDirectory(ResultSettings resultSettings, string description, string jsonGroup) => + GetResultsDateGroupDirectory(resultSettings, description, jsonGroup); + static string GetResultsDateGroupDirectory(ResultSettings resultSettings, string description, string jsonGroup) => + XResult.GetResultsDateGroupDirectory(resultSettings, description, jsonGroup); - List TestStatic_GetDirectoryInfoCollection(ResultConfiguration resultConfiguration, string sourceDirectory, string dateGroupDirectory, string contentDescription, string singletonDescription, string collectionDescription, bool converted) => - GetDirectoryInfoCollection(resultConfiguration, sourceDirectory, dateGroupDirectory, contentDescription, singletonDescription, collectionDescription, converted); - static List GetDirectoryInfoCollection(ResultConfiguration resultConfiguration, string sourceDirectory, string dateGroupDirectory, string contentDescription, string singletonDescription, string collectionDescription, bool converted) => - XResult.GetDirectoryInfoCollection(resultConfiguration, sourceDirectory, dateGroupDirectory, contentDescription, singletonDescription, collectionDescription, converted); + List TestStatic_GetDirectoryInfoCollection(ResultSettings resultSettings, string sourceDirectory, string dateGroupDirectory, string contentDescription, string singletonDescription, string collectionDescription, bool converted) => + GetDirectoryInfoCollection(resultSettings, sourceDirectory, dateGroupDirectory, contentDescription, singletonDescription, collectionDescription, converted); + static List GetDirectoryInfoCollection(ResultSettings resultSettings, string sourceDirectory, string dateGroupDirectory, string contentDescription, string singletonDescription, string collectionDescription, bool converted) => + XResult.GetDirectoryInfoCollection(resultSettings, sourceDirectory, dateGroupDirectory, contentDescription, singletonDescription, collectionDescription, converted); - string TestStatic_GetResultsFullGroupDirectory(ResultConfiguration resultConfiguration, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel) => - GetResultsFullGroupDirectory(resultConfiguration, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel); - static string GetResultsFullGroupDirectory(ResultConfiguration resultConfiguration, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel) => - XResult.GetResultsFullGroupDirectory(resultConfiguration, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel); + string TestStatic_GetResultsFullGroupDirectory(ResultSettings resultSettings, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel) => + GetResultsFullGroupDirectory(resultSettings, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel); + static string GetResultsFullGroupDirectory(ResultSettings resultSettings, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel) => + XResult.GetResultsFullGroupDirectory(resultSettings, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel); - List TestStatic_GetDirectoryInfoCollection(ResultConfiguration resultConfiguration, string sourceDirectory, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel, string contentDescription, string singletonDescription, string collectionDescription) => - GetDirectoryInfoCollection(resultConfiguration, sourceDirectory, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel, contentDescription, singletonDescription, collectionDescription); - static List GetDirectoryInfoCollection(ResultConfiguration resultConfiguration, string sourceDirectory, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel, string contentDescription, string singletonDescription, string collectionDescription) => - XResult.GetDirectoryInfoCollection(resultConfiguration, sourceDirectory, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel, contentDescription, singletonDescription, collectionDescription); + List TestStatic_GetDirectoryInfoCollection(ResultSettings resultSettings, string sourceDirectory, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel, string contentDescription, string singletonDescription, string collectionDescription) => + GetDirectoryInfoCollection(resultSettings, sourceDirectory, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel, contentDescription, singletonDescription, collectionDescription); + static List GetDirectoryInfoCollection(ResultSettings resultSettings, string sourceDirectory, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel, string contentDescription, string singletonDescription, string collectionDescription) => + XResult.GetDirectoryInfoCollection(resultSettings, sourceDirectory, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel, contentDescription, singletonDescription, collectionDescription); } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/Location.cs b/Shared/Models/Stateless/Methods/Location.cs new file mode 100644 index 0000000..e0f61f6 --- /dev/null +++ b/Shared/Models/Stateless/Methods/Location.cs @@ -0,0 +1,78 @@ +using System.Drawing; + +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +internal abstract class Location +{ + + internal static bool Check(int bottom, int left, int right, int top, int zCount, bool throwException) + { + bool result = true; + if (left < 0) + result = false; + if (right < 0) + result = false; + if (right < left) + result = false; + if (top < 0) + result = false; + if (bottom < 0) + result = false; + if (bottom < top) + result = false; + if (zCount < 0) + result = false; + if (throwException && !result) + throw new Exception(); + return result; + } + + internal static bool Check(int bottom, int height, int left, int right, int top, int width, int zCount, bool throwException) + { + bool result = true; + if (bottom > height) + result = false; + if (left > width) + result = false; + if (right > width) + result = false; + if (top > height) + result = false; + if (zCount < 0) + result = false; + if (result) + result = Check(bottom, left, right, top, zCount, throwException); + if (throwException && !result) + throw new Exception(); + return result; + } + + internal static RectangleF? GetPercentagesRectangle(DistanceSettings distanceSettings, int wholePercentages) + { + RectangleF? result; + string wp = wholePercentages.ToString(); + int length = (distanceSettings.LocationDigits - 1) / 4; + string[] segments = + [ + wp[..1], + wp.Substring(1, length), + wp.Substring(3, length), + wp.Substring(5, length), + wp.Substring(7, length) + ]; + if (string.Join(string.Empty, segments) != wp) + result = null; + else + { + if (!int.TryParse(segments[1], out int xWholePercent) || !int.TryParse(segments[2], out int yWholePercent) || !int.TryParse(segments[3], out int wWholePercent) || !int.TryParse(segments[4], out int hWholePercent)) + result = null; + else + { + float factor = 100; + result = new(xWholePercent / factor, yWholePercent / factor, wWholePercent / factor, hWholePercent / factor); + } + } + return result; + } + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/Mapping.cs b/Shared/Models/Stateless/Methods/Mapping.cs new file mode 100644 index 0000000..4e9c30f --- /dev/null +++ b/Shared/Models/Stateless/Methods/Mapping.cs @@ -0,0 +1,61 @@ +using View_by_Distance.Shared.Models.Properties; + +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +internal abstract class Mapping +{ + + internal static (string?, string?, bool?) GetSegments(string facesFileNameExtension, FilePath filePath) + { + string? extensionLowered; + string? wholePercentages; + bool? needsFacesFileNameExtension; + string[] segments = filePath.Name.Split('.'); + if (segments.Length < 4 || $".{segments[3]}" != facesFileNameExtension) + { + extensionLowered = null; + wholePercentages = null; + needsFacesFileNameExtension = null; + } + else + { + extensionLowered = $".{segments[2]}"; + wholePercentages = segments[1]; + needsFacesFileNameExtension = segments.Length == 3; + } + return new(wholePercentages, extensionLowered, needsFacesFileNameExtension); + } + + private static int? GetConvertedFromSegments(ICompareSettings compareSettings, FilePath filePath) + { + int? result; + (string? WholePercentages, string? ExtensionLowered, bool? Check) segments = GetSegments(compareSettings.FacesFileNameExtension, filePath); + if (string.IsNullOrEmpty(segments.WholePercentages) || string.IsNullOrEmpty(segments.ExtensionLowered) || segments.Check is null) + result = null; + else if (!int.TryParse(segments.WholePercentages, out int wholePercentages)) + result = null; + else + result = wholePercentages; + return result; + } + + internal static int? GetWholePercentages(ICompareSettings compareSettings, FilePath filePath) + { + int? wholePercentages; + if (filePath.Name.Length < 2 || filePath.Name[1..].Contains('-')) + wholePercentages = null; + else + wholePercentages = GetConvertedFromSegments(compareSettings, filePath); + return wholePercentages; + } + + internal static int GetAreaPermyriad(int faceAreaPermyriad, int bottom, int height, int left, int right, int top, int width) + { + int result; + double area = width * height; + double locationArea = (right - left) * (bottom - top); + result = (int)Math.Round(locationArea / area * faceAreaPermyriad, 0); + return result; + } + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/PersonBirthday.cs b/Shared/Models/Stateless/Methods/PersonBirthday.cs new file mode 100644 index 0000000..8f59309 --- /dev/null +++ b/Shared/Models/Stateless/Methods/PersonBirthday.cs @@ -0,0 +1,187 @@ +using System.Globalization; + +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +internal abstract class PersonBirthday +{ + + // ... + + internal static Models.PersonBirthday? GetPersonBirthday(string personBirthdayFormat, string personKeyFormatted) + { + Models.PersonBirthday? result; + DateTime? dateTime; + if (personKeyFormatted.Length != personBirthdayFormat.Length) + result = null; + else + { + dateTime = IPersonBirthday.GetDateTime(personBirthdayFormat, personKeyFormatted); + if (dateTime is null) + result = null; + else + result = new(dateTime.Value); + } + return result; + } + + internal static TimeSpan? GetTimeSpan(long minimumDateTimeTicks, bool? isWrongYear, Models.PersonBirthday personBirthday) + { + TimeSpan? timeSpan; + bool isWrongYearFilterOrCounterPersonBirthday = IPersonBirthday.IsWrongYearFilterOrCounterPersonBirthday(isWrongYear, personBirthday); + if (isWrongYearFilterOrCounterPersonBirthday) + timeSpan = null; + else + timeSpan = new(minimumDateTimeTicks - personBirthday.Value.Ticks); + return timeSpan; + } + + internal static TimeSpan? GetTimeSpan(DateTime minimumDateTime, bool? isWrongYear, Models.PersonBirthday personBirthday) + { + TimeSpan? timeSpan = GetTimeSpan(minimumDateTime.Ticks, isWrongYear, personBirthday); + return timeSpan; + } + + internal static (int, TimeSpan) GetAge(long dateTimeTicks, Models.PersonBirthday birthday) + { + TimeSpan result; + int years; + if (birthday?.Value is null) + throw new NullReferenceException(nameof(birthday.Value)); + (years, result) = Age.GetAge(dateTimeTicks, birthday.Value); + return (years, result); + } + + internal static (int, TimeSpan) GetAge(DateTime dateTime, Models.PersonBirthday birthday) + { + TimeSpan result; + int years; + if (birthday?.Value is null) + throw new NullReferenceException(nameof(birthday.Value)); + (years, result) = Age.GetAge(dateTime, birthday.Value); + return (years, result); + } + + internal static (int, double) GetAge(DateTime dateTime, DateTime dayBeforeLeapDate, Models.PersonBirthday birthday) + { + double result; + (int years, TimeSpan timeSpan) = GetAge(dateTime, birthday); + if (!DateTime.IsLeapYear(dateTime.Year) || dateTime < dayBeforeLeapDate.AddDays(1)) + result = timeSpan.TotalDays / 365; + else + result = timeSpan.TotalDays / 366; + return (years, result); + } + + internal static double? GetAge(Models.PersonBirthday birthday) + { + double? result; + if (birthday is null) + result = null; + else + { + DateTime dateTime = DateTime.Now; + DateTime dayBeforeLeapDate = new(dateTime.Year, 2, 28); + (int years, double r) = GetAge(dateTime, dayBeforeLeapDate, birthday); + result = years + r; + } + return result; + } + + internal static List GetPersonBirthdays(string personBirthdayFormat, string[] personKeyDirectories, string personDisplayDirectoryName) + { + List results = []; + string personKeyFormatted; + Models.PersonBirthday? personBirthday; + PersonKeyFormattedAndPersonBirthday personKeyFormattedAndPersonBirthday; + foreach (string personKeyDirectory in personKeyDirectories) + { + personKeyFormatted = Path.GetFileName(personKeyDirectory); + if (!DateTime.TryParseExact(personKeyFormatted, "MM.dd.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime birthday)) + personBirthday = IPersonBirthday.GetPersonBirthday(personBirthdayFormat, personKeyFormatted); + else + // (personBirthday, personKeyFormatted) = Person.Get(personBirthdayFormat, personDisplayDirectory, personKeyDirectory, birthday); + continue; + if (personBirthday is null) + continue; + if (!IPersonBirthday.IsCounterPersonBirthday(personBirthday) && ((!personKeyDirectory.Contains('#') && (personDisplayDirectoryName.Contains('~') || personDisplayDirectoryName.Contains('#'))) || (personKeyDirectory.Contains('#') && !personDisplayDirectoryName.Contains('#')))) + throw new NotSupportedException(); + personKeyFormattedAndPersonBirthday = new(personKeyFormatted, personBirthday); + results.Add(personKeyFormattedAndPersonBirthday); + } + return results; + } + + private static string? GetMonthShortForm(string month) + { + string? result = month.ToLower()[0] switch + { + // 'j' => "jan", + 'f' => "feb", + // 'm' => "mar", + // 'a' => "apr", + // 'm' => "may", + // 'j' => "jun", + // 'j' => "jul", + // 'a' => "aug", + 's' => "sep", + 'o' => "oct", + 'n' => "nov", + 'd' => "dec", + _ => null + }; + return result; + } + + internal static DateTime? GetDate(string month, string day, string year) + { + DateTime? result; + DateTime dayDateTime; + DateTime yearDateTime; + DateTime monthDateTime; + string? monthShortHand = string.IsNullOrEmpty(month) ? "x" : GetMonthShortForm(month); + if (month.Length > 3) + { + if (!DateTime.TryParseExact($"{month},1,1500", "MMMM,d,yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out monthDateTime)) + monthDateTime = DateTime.MinValue; + } + else if (month.Length == 3) + { + if (!DateTime.TryParseExact($"{month},1,1500", "MMM,d,yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out monthDateTime)) + monthDateTime = DateTime.MinValue; + } + else if (month.Length == 1 && monthShortHand is not null) + { + if (!DateTime.TryParseExact($"{monthShortHand},1,1500", "MMM,d,yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out monthDateTime)) + monthDateTime = DateTime.MinValue; + } + else if (int.TryParse(month, out int _)) + { + if (!DateTime.TryParseExact($"{month.PadLeft(2, '0')[..2]},1,1500", "MM,d,yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out monthDateTime)) + monthDateTime = DateTime.MinValue; + } + else + monthDateTime = DateTime.MinValue; + if (!int.TryParse(day, out int _)) + dayDateTime = DateTime.MinValue; + else + { + if (!DateTime.TryParseExact($"01,{day.PadLeft(2, '0')[..2]},1500", "MM,d,yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out dayDateTime)) + dayDateTime = DateTime.MinValue; + } + if (year.Length == 2 && int.TryParse(year, out int _)) + { + if (!DateTime.TryParseExact($"01,01,{year.PadLeft(4, '0')[..4]}", "MM,dd,yy", CultureInfo.InvariantCulture, DateTimeStyles.None, out yearDateTime)) + yearDateTime = DateTime.MinValue; + } + else if (year.Length == 4 && int.TryParse(year, out int _)) + { + if (!DateTime.TryParseExact($"01,01,{year.PadLeft(4, '0')[..4]}", "MM,dd,yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out yearDateTime)) + yearDateTime = DateTime.MinValue; + } + else + yearDateTime = DateTime.MinValue; + result = monthDateTime == DateTime.MinValue ? null : dayDateTime == DateTime.MinValue ? null : yearDateTime == DateTime.MinValue ? null : new(yearDateTime.Year, monthDateTime.Month, dayDateTime.Day); + return result; + } + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Mode.cs b/Shared/Models/Stateless/Mode.cs new file mode 100644 index 0000000..bf9f996 --- /dev/null +++ b/Shared/Models/Stateless/Mode.cs @@ -0,0 +1,19 @@ +namespace View_by_Distance.Shared.Models.Stateless; + +/// +/// Specifies the image mode. +/// +public enum Mode +{ + + /// + /// Specifies that the rgb (8-bit Red, Green and Blue, 3 channels) image mode. + /// + Rgb, + + /// + /// Specifies that the greyscale image mode. + /// + Greyscale + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/XDate.cs b/Shared/Models/Stateless/XDate.cs index ef22a82..3ef8a4f 100644 --- a/Shared/Models/Stateless/XDate.cs +++ b/Shared/Models/Stateless/XDate.cs @@ -7,6 +7,8 @@ namespace View_by_Distance.Shared.Models.Stateless; internal abstract class XDate { + private record Record(bool? IsWrongYear, string[] Years); + internal static (int Season, string seasonName) GetSeason(int dayOfYear) { (int Season, string seasonName) result = dayOfYear switch @@ -23,9 +25,10 @@ internal abstract class XDate return result; } - private static (bool?, string[]) IsWrongYear(string[] segments, string year) + private static Record IsWrongYear(string[] segments, string year) { - bool? result; + Record result; + bool? check; string[] results = ( from l in segments @@ -48,10 +51,11 @@ internal abstract class XDate select l ).ToArray(); if (results.Length == 0) - result = null; + check = null; else - result = matches.Length == 0; - return new(result, results); + check = matches.Length == 0; + result = new(check, results); + return result; } internal static (bool?, string[]) IsWrongYear(FilePath filePath, ExifDirectory exifDirectory) @@ -215,7 +219,7 @@ internal abstract class XDate } if (results.Count == 0) { - string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(exifDirectory.OriginalFileName); + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(exifDirectory.FilePath.Name); DateTime? dateTime = GetDateTimeFromName(fileNameWithoutExtension); if (dateTime is not null) results.Add(dateTime.Value); @@ -235,7 +239,7 @@ internal abstract class XDate results.Add(fileMetadataDirectory.FileModifiedDate.Value); } } - return new(results); + return results.AsReadOnly(); } internal static DateTime GetMinimum(ExifDirectory exifDirectory) diff --git a/Shared/Models/Stateless/XPath.cs b/Shared/Models/Stateless/XPath.cs index 69580ee..615aab9 100644 --- a/Shared/Models/Stateless/XPath.cs +++ b/Shared/Models/Stateless/XPath.cs @@ -284,36 +284,36 @@ internal abstract class XPath return (result, converted); } - internal static (string, int) GetDirectoryNameAndIndex(ResultConfiguration resultConfiguration, int id) + internal static (string, int) GetDirectoryNameAndIndex(ResultSettings resultSettings, int id) { - (string result, int converted) = GetDirectoryNameAndIndex(resultConfiguration.ResultAllInOneSubdirectoryLength, id.ToString()); + (string result, int converted) = GetDirectoryNameAndIndex(resultSettings.ResultAllInOneSubdirectoryLength, id.ToString()); return (result, converted); } - internal static (string, int) GetDirectoryNameAndIndex(ResultConfiguration resultConfiguration, FileHolder fileHolder) + internal static (string, int) GetDirectoryNameAndIndex(ResultSettings resultSettings, FileHolder fileHolder) { - (string result, int converted) = GetDirectoryNameAndIndex(resultConfiguration.ResultAllInOneSubdirectoryLength, fileHolder.NameWithoutExtension); + (string result, int converted) = GetDirectoryNameAndIndex(resultSettings.ResultAllInOneSubdirectoryLength, fileHolder.NameWithoutExtension); return (result, converted); } - internal static (string, int) GetDirectoryNameAndIndex(ResultConfiguration resultConfiguration, FilePath filePath) + internal static (string, int) GetDirectoryNameAndIndex(ResultSettings resultSettings, FilePath filePath) { string result; int converted; if (filePath.Id is not null) - (result, converted) = GetDirectoryNameAndIndex(resultConfiguration.ResultAllInOneSubdirectoryLength, filePath.Id.Value.ToString()); + (result, converted) = GetDirectoryNameAndIndex(resultSettings.ResultAllInOneSubdirectoryLength, filePath.Id.Value.ToString()); else - (result, converted) = GetDirectoryNameAndIndex(resultConfiguration.ResultAllInOneSubdirectoryLength, filePath.NameWithoutExtension); + (result, converted) = GetDirectoryNameAndIndex(resultSettings.ResultAllInOneSubdirectoryLength, filePath.NameWithoutExtension); return (result, converted); } - private static ReadOnlyCollection GetYears(ResultConfiguration resultConfiguration) + private static ReadOnlyCollection GetYears(ResultSettings resultSettings) { List results = []; int currentYear = DateTime.Now.Year; - for (int i = resultConfiguration.EpicYear; i < currentYear + 1; i++) + for (int i = resultSettings.EpicYear; i < currentYear + 1; i++) results.Add(i); - return new(results); + return results.AsReadOnly(); } private static ReadOnlyDictionary> Convert(Dictionary> collection) @@ -321,17 +321,17 @@ internal abstract class XPath Dictionary> results = []; foreach (KeyValuePair> keyValuePair in collection) results.Add(keyValuePair.Key, new(keyValuePair.Value)); - return new(results); + return results.AsReadOnly(); } - internal static ReadOnlyDictionary> GetKeyValuePairs(ResultConfiguration resultConfiguration, string? resultsFullGroupDirectory, string[]? jsonGroups) + internal static ReadOnlyDictionary> GetKeyValuePairs(ResultSettings resultSettings, string? resultsFullGroupDirectory, string[]? jsonGroups) { Dictionary> results = []; string directory; string checkDirectory; Dictionary? keyValuePairs; - ReadOnlyCollection years = GetYears(resultConfiguration); - int converted = int.Parse($"1{new string('0', resultConfiguration.ResultAllInOneSubdirectoryLength)}"); + ReadOnlyCollection years = GetYears(resultSettings); + int converted = int.Parse($"1{new string('0', resultSettings.ResultAllInOneSubdirectoryLength)}"); int plusOne = converted + 1; List collection = []; foreach (int year in years) @@ -351,17 +351,17 @@ internal abstract class XPath if (string.IsNullOrEmpty(jsonGroup)) { if (i == converted) - checkDirectory = Path.GetFullPath(Path.Combine(resultsFullGroupDirectory, new('-', resultConfiguration.ResultAllInOneSubdirectoryLength))); + checkDirectory = Path.GetFullPath(Path.Combine(resultsFullGroupDirectory, new('-', resultSettings.ResultAllInOneSubdirectoryLength))); else - checkDirectory = Path.GetFullPath(Path.Combine(resultsFullGroupDirectory, i.ToString().PadLeft(resultConfiguration.ResultAllInOneSubdirectoryLength, '0'))); + checkDirectory = Path.GetFullPath(Path.Combine(resultsFullGroupDirectory, i.ToString().PadLeft(resultSettings.ResultAllInOneSubdirectoryLength, '0'))); } else { directory = Path.Combine(resultsFullGroupDirectory, jsonGroup); if (i == converted) - checkDirectory = Path.GetFullPath(Path.Combine(directory, new('-', resultConfiguration.ResultAllInOneSubdirectoryLength))); + checkDirectory = Path.GetFullPath(Path.Combine(directory, new('-', resultSettings.ResultAllInOneSubdirectoryLength))); else - checkDirectory = Path.GetFullPath(Path.Combine(directory, i.ToString().PadLeft(resultConfiguration.ResultAllInOneSubdirectoryLength, '0'))); + checkDirectory = Path.GetFullPath(Path.Combine(directory, i.ToString().PadLeft(resultSettings.ResultAllInOneSubdirectoryLength, '0'))); } if (!Directory.Exists(checkDirectory)) _ = Directory.CreateDirectory(checkDirectory); diff --git a/Shared/Models/Stateless/XResult.cs b/Shared/Models/Stateless/XResult.cs index 78ed76d..6f9a521 100644 --- a/Shared/Models/Stateless/XResult.cs +++ b/Shared/Models/Stateless/XResult.cs @@ -3,33 +3,33 @@ namespace View_by_Distance.Shared.Models.Stateless; internal abstract class XResult { - internal static string GetResultsDateGroupDirectory(ResultConfiguration resultConfiguration, string description, string jsonGroup) + internal static string GetResultsDateGroupDirectory(ResultSettings resultSettings, string description, string jsonGroup) { - string result = Path.Combine(GetResultsDateGroupDirectory(resultConfiguration, description), jsonGroup); + string result = Path.Combine(GetResultsDateGroupDirectory(resultSettings, description), jsonGroup); if (!Directory.Exists(result)) _ = Directory.CreateDirectory(result); return result; } - internal static string GetResultsDateGroupDirectory(ResultConfiguration resultConfiguration, string description) + internal static string GetResultsDateGroupDirectory(ResultSettings resultSettings, string description) { - string result = Path.Combine(GetResultsGroupDirectory(resultConfiguration, description, create: true), resultConfiguration.DateGroup); + string result = Path.Combine(GetResultsGroupDirectory(resultSettings, description, create: true), resultSettings.DateGroup); if (!Directory.Exists(result)) _ = Directory.CreateDirectory(result); return result; } - internal static string GetRelativePath(ResultConfiguration resultConfiguration, string path) + internal static string GetRelativePath(ResultSettings resultSettings, string path) { - string result = Methods.IPath.GetRelativePath(path, resultConfiguration.RootDirectory.Length); + string result = Methods.IPath.GetRelativePath(path, resultSettings.RootDirectory.Length); return result; } - private static void VerifyDirectories(ResultConfiguration resultConfiguration, string dateGroupDirectory, string jsonGroup) + private static void VerifyDirectories(ResultSettings resultSettings, string dateGroupDirectory, string jsonGroup) { string checkDirectory; int currentYear = DateTime.Now.Year; - for (int i = resultConfiguration.EpicYear; i < currentYear + 1; i++) + for (int i = resultSettings.EpicYear; i < currentYear + 1; i++) { checkDirectory = Path.Combine(dateGroupDirectory, jsonGroup, i.ToString()); if (!Directory.Exists(checkDirectory)) @@ -37,10 +37,10 @@ internal abstract class XResult } } - private static void CheckContent(ResultConfiguration resultConfiguration, string dateGroupDirectory, string contentDescription, string result) + private static void CheckContent(ResultSettings resultSettings, string dateGroupDirectory, string contentDescription, string result) { - VerifyDirectories(resultConfiguration, dateGroupDirectory, resultConfiguration.ResultContent); - string contentDirectory = new(result.Replace("<>", resultConfiguration.ResultContent)); + VerifyDirectories(resultSettings, dateGroupDirectory, resultSettings.ResultContent); + string contentDirectory = new(result.Replace("<>", resultSettings.ResultContent)); if (!Directory.Exists(contentDirectory)) _ = Directory.CreateDirectory(contentDirectory); string checkDirectory = Path.Combine(dateGroupDirectory, string.Concat("() - ", contentDescription)); @@ -48,12 +48,12 @@ internal abstract class XResult _ = Directory.CreateDirectory(checkDirectory); } - private static void CheckSingleton(ResultConfiguration resultConfiguration, string dateGroupDirectory, string singletonDescription, bool converted, string result) + private static void CheckSingleton(ResultSettings resultSettings, string dateGroupDirectory, string singletonDescription, bool converted, string result) { - VerifyDirectories(resultConfiguration, dateGroupDirectory, resultConfiguration.ResultSingleton); + VerifyDirectories(resultSettings, dateGroupDirectory, resultSettings.ResultSingleton); if (!converted) { - string singletonDirectory = new(result.Replace("<>", resultConfiguration.ResultSingleton)); + string singletonDirectory = new(result.Replace("<>", resultSettings.ResultSingleton)); if (!Directory.Exists(singletonDirectory)) _ = Directory.CreateDirectory(singletonDirectory); } @@ -62,12 +62,12 @@ internal abstract class XResult _ = Directory.CreateDirectory(checkDirectory); } - private static void CheckCollection(ResultConfiguration resultConfiguration, string dateGroupDirectory, string collectionDescription, bool converted, string result) + private static void CheckCollection(ResultSettings resultSettings, string dateGroupDirectory, string collectionDescription, bool converted, string result) { - VerifyDirectories(resultConfiguration, dateGroupDirectory, resultConfiguration.ResultCollection); + VerifyDirectories(resultSettings, dateGroupDirectory, resultSettings.ResultCollection); if (!converted) { - string collectionDirectory = new(result.Replace("<>", resultConfiguration.ResultCollection)); + string collectionDirectory = new(result.Replace("<>", resultSettings.ResultCollection)); if (!Directory.Exists(collectionDirectory)) _ = Directory.CreateDirectory(collectionDirectory); } @@ -76,24 +76,24 @@ internal abstract class XResult _ = Directory.CreateDirectory(checkDirectory); } - internal static string GetResultsFullGroupDirectory(ResultConfiguration resultConfiguration, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel) + internal static string GetResultsFullGroupDirectory(ResultSettings resultSettings, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel) { - string result = GetResultsDateGroupDirectory(resultConfiguration, description); + string result = GetResultsDateGroupDirectory(resultSettings, description); if (includeResizeGroup) result = Path.Combine(result, outputResolution); if (includeModel && includePredictorModel) { string modelName; string predictorModelName; - if (resultConfiguration.ModelName is null) + if (resultSettings.ModelName is null) modelName = Model.Hog.ToString(); else - modelName = resultConfiguration.ModelName; - if (resultConfiguration.PredictorModelName is null) + modelName = resultSettings.ModelName; + if (resultSettings.PredictorModelName is null) predictorModelName = PredictorModel.Large.ToString(); else - predictorModelName = resultConfiguration.PredictorModelName; - string dateGroupDirectory = string.Concat(outputResolution.Replace(" ", string.Empty), "-", modelName, "-", predictorModelName, "-", resultConfiguration.NumberOfJitters, "-", resultConfiguration.NumberOfTimesToUpsample); + predictorModelName = resultSettings.PredictorModelName; + string dateGroupDirectory = string.Concat(outputResolution.Replace(" ", string.Empty), "-", modelName, "-", predictorModelName, "-", resultSettings.NumberOfJitters, "-", resultSettings.NumberOfTimesToUpsample); result = Path.Combine(result, dateGroupDirectory); } else if (includeModel) @@ -105,35 +105,35 @@ internal abstract class XResult return result; } - internal static List GetDirectoryInfoCollection(ResultConfiguration resultConfiguration, string sourceDirectory, string dateGroupDirectory, string contentDescription, string singletonDescription, string collectionDescription, bool converted) + internal static List GetDirectoryInfoCollection(ResultSettings resultSettings, string sourceDirectory, string dateGroupDirectory, string contentDescription, string singletonDescription, string collectionDescription, bool converted) { List results = []; - string sourceDirectorySegment = GetRelativePath(resultConfiguration, sourceDirectory); + string sourceDirectorySegment = GetRelativePath(resultSettings, sourceDirectory); string result = string.Concat(Path.Combine(dateGroupDirectory, "<>"), sourceDirectorySegment); if (!string.IsNullOrEmpty(contentDescription)) - CheckContent(resultConfiguration, dateGroupDirectory, contentDescription, result); + CheckContent(resultSettings, dateGroupDirectory, contentDescription, result); if (!string.IsNullOrEmpty(singletonDescription)) - CheckSingleton(resultConfiguration, dateGroupDirectory, singletonDescription, converted, result); + CheckSingleton(resultSettings, dateGroupDirectory, singletonDescription, converted, result); if (!string.IsNullOrEmpty(collectionDescription)) - CheckCollection(resultConfiguration, dateGroupDirectory, collectionDescription, converted, result); + CheckCollection(resultSettings, dateGroupDirectory, collectionDescription, converted, result); results.Add(result); return results; } - internal static string GetResultsGroupDirectory(ResultConfiguration resultConfiguration, string description, bool create) + internal static string GetResultsGroupDirectory(ResultSettings resultSettings, string description, bool create) { - string result = Path.Combine($"{resultConfiguration.RootDirectory}-Results", description.Replace('_', ')')); + string result = Path.Combine($"{resultSettings.RootDirectory}-Results", description.Replace('_', ')')); if (create && !Directory.Exists(result)) _ = Directory.CreateDirectory(result); return result; } - internal static List GetDirectoryInfoCollection(ResultConfiguration resultConfiguration, string sourceDirectory, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel, string contentDescription, string singletonDescription, string collectionDescription) + internal static List GetDirectoryInfoCollection(ResultSettings resultSettings, string sourceDirectory, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel, string contentDescription, string singletonDescription, string collectionDescription) { List results; bool converted = false; - string dateGroupDirectory = GetResultsFullGroupDirectory(resultConfiguration, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel); - results = GetDirectoryInfoCollection(resultConfiguration, sourceDirectory, dateGroupDirectory, contentDescription, singletonDescription, collectionDescription, converted); + string dateGroupDirectory = GetResultsFullGroupDirectory(resultSettings, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel); + results = GetDirectoryInfoCollection(resultSettings, sourceDirectory, dateGroupDirectory, contentDescription, singletonDescription, collectionDescription, converted); return results; }