Compare commits

37 Commits

Author SHA1 Message Date
ef4672aaf0 Removed extra method 2025-03-02 08:39:32 -07:00
3ea4926f5e Ready to test Windows Project 2025-02-16 21:30:17 -07:00
039355f31e net9.0 2025-02-15 12:18:58 -07:00
68dff9c24c Nuget Pack
net8
2025-02-08 17:03:02 -07:00
0fa899f65d Bug fix from
Single Level AppSettings
2025-01-04 15:35:03 -07:00
17532d2698 Using PersonContainer for PersonDisplayDirectoryName 2024-12-29 11:38:24 -07:00
1f8c4569ee Removed Methods Namespace 2024-12-29 10:46:13 -07:00
0215e838e7 AA.Compare Project to Match not runToDoCollectionFirst
Removed Layered AppSettings with Nested Objects at First Level
2024-12-28 19:34:09 -07:00
3ff8153393 net9.0 2024-11-17 10:32:00 -07:00
7b8ab5508b Use ValidVideoFormatExtensions to move when not forcing Id 2024-11-03 09:14:42 -07:00
368390b353 InPlaceMoveDirectory
DirectoryName => DirectoryFullPath
2024-11-02 14:43:53 -07:00
2f75667f20 Fast Forward Moving Picture Experts Group Used 2024-10-30 17:33:36 -07:00
924bd14c31 FaceFile for D2 2024-10-20 17:49:48 -07:00
7b00fcaae8 Identifier new properties 2024-10-14 08:48:30 -07:00
fc891afe2c Bump 2024-10-14 08:26:53 -07:00
f6e6fa712f Bump 2024-08-17 10:58:54 -07:00
a878fe3253 Bump 2024-07-19 12:00:44 -07:00
2a305c69f0 #pragma warning disable IDE0060 2024-06-24 17:20:45 -07:00
8a817750c9 Prep for item.FilePath.HasIgnoreKeyword, item.FilePath.HasDateTimeOriginal 2024-06-17 21:41:37 -07:00
a9275dd5d1 Pending changes 2024-06-17 21:01:57 -07:00
dc1b0e3efd LogInformation 2024-05-21 18:59:37 -07:00
2661c4174c Bump 2024-05-21 18:57:49 -07:00
9521aee8c6 InPlace 2024-05-21 18:56:47 -07:00
b9ed5ee159 JsonSerializer.Deserialize for AppSettings 2024-05-21 16:23:45 -07:00
cd88e340a3 Format
Try when running synchronously
2024-05-19 22:30:33 -07:00
c838848129 OnlySaveIdentifiersToDisk 2024-05-19 20:10:57 -07:00
30b8e2f5a9 Sidecar 2024-05-18 17:06:47 -07:00
abbe2feac0 Removed Amazon
IsOffsetDeterministicHashCode
2024-05-18 10:26:36 -07:00
d1557e1d85 RelativePropertyCollectionFile
aMetadataCollectionDirectory
Work with video
FilePath.IsIgnore
Removed IId IsIgnore
Keywords
RootAmazon
SaveAmazon
PhysicalFileProvider Message
Bump
HarFilesDirectory
2024-04-28 17:13:28 -07:00
684ba1f0df Metadata 2023-12-26 18:27:04 -07:00
7ec50dd56e RenameUrl 2023-12-25 20:41:10 -07:00
d830c1d5a4 ValidVideoFormatExtensions 2023-12-25 19:18:54 -07:00
48eea2edbd Removed IntelligentIdRecord 2023-12-24 08:59:45 -07:00
1f00696bf3 Bug 2023-12-22 16:56:40 -07:00
82de27ce61 Videos 2023-12-22 16:22:08 -07:00
96c479e639 IntelligentIdRecord 2023-12-22 15:46:47 -07:00
0310e06f3c Used to re-organized folders 2023-11-12 18:09:22 -07:00
163 changed files with 9878 additions and 2011 deletions

View File

@ -82,28 +82,50 @@ csharp_style_var_elsewhere = false:warning
csharp_style_var_for_built_in_types = false:warning
csharp_style_var_when_type_is_apparent = false:warning
csharp_using_directive_placement = outside_namespace
dotnet_analyzer_diagnostic.category-Design.severity = error
dotnet_analyzer_diagnostic.category-Documentation.severity = error
dotnet_analyzer_diagnostic.category-Globalization.severity = none
dotnet_analyzer_diagnostic.category-Interoperability.severity = error
dotnet_analyzer_diagnostic.category-Maintainability.severity = error
dotnet_analyzer_diagnostic.category-Naming.severity = none
dotnet_analyzer_diagnostic.category-Performance.severity = none
dotnet_analyzer_diagnostic.category-Reliability.severity = error
dotnet_analyzer_diagnostic.category-Security.severity = error
dotnet_analyzer_diagnostic.category-SingleFile.severity = error
dotnet_analyzer_diagnostic.category-Style.severity = error
dotnet_analyzer_diagnostic.category-Usage.severity = error
dotnet_code_quality_unused_parameters = all
dotnet_code_quality_unused_parameters = non_public # IDE0060: Remove unused parameter
dotnet_code_quality.CAXXXX.api_surface = private, internal
dotnet_diagnostic.CA1001.severity = error # CA1001: Types that own disposable fields should be disposable
dotnet_diagnostic.CA1051.severity = error # CA1051: Do not declare visible instance fields
dotnet_diagnostic.CA1825.severity = warning # CA1823: Avoid zero-length array allocations
dotnet_diagnostic.CA1829.severity = warning # CA1829: Use Length/Count property instead of Count() when available
dotnet_diagnostic.CA1834.severity = warning # CA1834: Consider using 'StringBuilder.Append(char)' when applicable
dotnet_diagnostic.CA1860.severity = error # CA1860: Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance
dotnet_diagnostic.CA1869.severity = none # CA1869: Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead.
dotnet_diagnostic.CA2254.severity = none # CA2254: The logging message template should not vary between calls to 'LoggerExtensions.LogInformation(ILogger, string?, params object?[])'
dotnet_diagnostic.CA1861.severity = error # CA1861: Prefer 'static readonly' fields over constant array arguments
dotnet_diagnostic.CA1869.severity = error # CA1869: Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead.
dotnet_diagnostic.CA2201.severity = none # CA2201: Exception type System.NullReferenceException is reserved by the runtime
dotnet_diagnostic.CA2254.severity = error # CA2254: The logging message template should not vary between calls to 'LoggerExtensions.LogInformation(ILogger, string?, params object?[])'
dotnet_diagnostic.IDE0001.severity = warning # IDE0001: Simplify name
dotnet_diagnostic.IDE0002.severity = warning # Simplify (member access) - System.Version.Equals("1", "2"); Version.Equals("1", "2");
dotnet_diagnostic.IDE0004.severity = warning # IDE0004: Cast is redundant.
dotnet_diagnostic.IDE0005.severity = warning # Using directive is unnecessary
dotnet_diagnostic.IDE0010.severity = error # Add missing cases to switch statement (IDE0010)
dotnet_diagnostic.IDE0028.severity = error # IDE0028: Collection initialization can be simplified
dotnet_diagnostic.IDE0031.severity = warning # Use null propagation (IDE0031)
dotnet_diagnostic.IDE0047.severity = warning # IDE0047: Parentheses can be removed
dotnet_diagnostic.IDE0048.severity = error # Parentheses preferences (IDE0047 and IDE0048)
dotnet_diagnostic.IDE0049.severity = warning # Use language keywords instead of framework type names for type references (IDE0049)
dotnet_diagnostic.IDE0051.severity = error # Private member '' is unused [, ]
dotnet_diagnostic.IDE0060.severity = warning # IDE0060: Remove unused parameter
dotnet_diagnostic.IDE0290.severity = none # Use primary constructor [Distance]csharp(IDE0290)
dotnet_diagnostic.IDE0130.severity = none # Namespace does not match folder structure (IDE0130)
dotnet_diagnostic.IDE0230.severity = warning # IDE0230: Use UTF-8 string literal
dotnet_diagnostic.IDE0290.severity = error # Use primary constructor [Distance]csharp(IDE0290)
dotnet_diagnostic.IDE0300.severity = error # IDE0300: Collection initialization can be simplified
dotnet_diagnostic.IDE0301.severity = error #IDE0301: Collection initialization can be simplified
dotnet_diagnostic.IDE0305.severity = none # IDE0305: Collection initialization can be simplified
dotnet_diagnostic.JSON002.severity = warning # JSON002: Probable JSON string detected
dotnet_naming_rule.abstract_method_should_be_pascal_case.severity = warning
dotnet_naming_rule.abstract_method_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.abstract_method_should_be_pascal_case.symbols = abstract_method

8
.gitignore vendored
View File

@ -468,3 +468,11 @@ globalStorage/
[Ll]ib/
Shared/.kanbn
.vscode/Har-Files
Compare/.vscode/.UserSecrets/secrets.json
Rename/.vscode/.UserSecrets/secrets.json
Rename/.vscode/.UserSecretsOld/rename.json
Rename/.vscode/.UserSecretsOld/secrets.json
Windows/.vscode/.UserSecrets/secrets.json
Windows/.vscode/.iCloudPhotos2025

0
.vscode/.yml vendored Normal file
View File

526
.vscode/launch.json vendored
View File

@ -1,302 +1,12 @@
{
"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/net8.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/net8.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/net8.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/net8.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/net8.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/net8.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/net8.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/net8.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/net8.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/net8.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/net8.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/net8.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/net8.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/net8.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/net8.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/net8.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/net8.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/net8.0/Person.dll",
"preLaunchTask": "Build-Compare",
"program": "${workspaceFolder}/Compare/bin/Debug/net9.0/win-x64/AA.Compare.dll",
"args": [
"s"
],
@ -312,8 +22,8 @@
"name": "Rename",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/Rename/bin/Debug/net8.0/win-x64/AA.Rename.dll",
"preLaunchTask": "Build-Rename",
"program": "${workspaceFolder}/Rename/bin/Debug/net9.0/win-x64/AA.Rename.dll",
"args": [
"s"
],
@ -326,11 +36,11 @@
"requireExactSource": false
},
{
"name": "Set-Created-Date",
"name": "Windows",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/Set-Created-Date/bin/Debug/net8.0/win-x64/Set-Created-Date.dll",
"preLaunchTask": "Build-Windows",
"program": "${workspaceFolder}/Windows/bin/Debug/net9.0/win-x64/AA.Windows.dll",
"args": [
"s"
],
@ -343,224 +53,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 <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-10-19 12.00.31.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-10-20 15.41.37.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-10-23 09.57.51.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-10-25 13.46.09.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-10-30 21.47.53.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-10-31 11.33.13.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-11-05 10.28.25.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-11-11 19.57.27.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-11-12 14.15.39.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-11-13 09.49.09.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-11-15 08.55.19.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-12-01 18.03.13.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-12-04 08.44.23.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-12-10 21.20.31.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2012\2012-12-16 18.17.53.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2013\IMG_20130317_113053.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2013\IMG_20130324_133916.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2013\IMG_20130330_114757.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2013\IMG_20130418_175845.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2013\IMG_20130418_175934.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2013\IMG_20130424_202523.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2013\IMG_20130424_202634.jpg>
// FileSizeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2013\IMG_20130514_190936.jpg>
// LastWriteTimeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2013\IMG_20130516_104450.jpg>
// LastWriteTimeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Kristy Google Photos from 2013\IMG_20130522_193539.jpg>
// LastWriteTimeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Herman Family Reunion 2008\Herman Family Reunion (160).jpg>
// LastWriteTimeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Herman Family Reunion 2008\Herman Family Reunion (213).jpg>
// LastWriteTimeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Herman Family Reunion 2008\Herman Family Reunion (215).jpg>
// LastWriteTimeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Herman Family Reunion 2008\Herman Family Reunion (218).jpg>
// LastWriteTimeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Herman Family Reunion 2008\Herman Family Reunion (225).jpg>
// LastWriteTimeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Herman Family Reunion 2008\Herman Family Reunion (226).jpg>
// LastWriteTimeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Herman Family Reunion 2008\Herman Family Reunion (231).jpg>
// LastWriteTimeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Herman Family Reunion 2008\Herman Family Reunion (232).jpg>
// LastWriteTimeChanged <F:\Tmp\Phares\Compare\Images-4d49b68\Herman Family Reunion 2008\Herman Family Reunion (234).jpg>
// - 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<string> 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") <Verified in Windows>
// 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", ...) <Verified in Windows>
// 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)
}

27
.vscode/mklink.md vendored
View File

@ -13,3 +13,30 @@ mklink /J "L:\Git\AA\Shared\.kanbn" "D:\5-Other-Small\Kanban\View-by-Distance"
```bash
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"
```
```bash 1736011475498 = 638716082754980000 = Sat Jan 04 2025 10:24:35 GMT-0700 (Mountain Standard Time)
mklink /J "L:\Git\AA\Rename\.vscode\.UserSecrets" "C:\Users\mikep\AppData\Roaming\Microsoft\UserSecrets\843db3e1-e18f-4cba-8b00-967529a32635"
mklink /J "L:\Git\AA\Compare\.vscode\.UserSecrets" "C:\Users\mikep\AppData\Roaming\Microsoft\UserSecrets\770b6ae3-266e-4d5f-970a-173709b064de"
mklink /J "L:\Git\AA\Windows\.vscode\.UserSecrets" "C:\Users\mikep\AppData\Roaming\Microsoft\UserSecrets\076c87e8-c7f0-40a3-aba3-73eb7f9ea892"
mklink /J "L:\Git\AA\Windows\.vscode\.iCloudPhotos2025" "D:\7-Question\iCloud Photos 2025"
```
```json 1735493575037 = 638710903750370000 = Sun Dec 29 2024 10:32:54 GMT-0700 (Mountain Standard Time)
{ "label": "Build-Compare", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/Compare/AA.Compare.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-Face", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/Face/AA.Face.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], "problemMatcher": "$msCompile" },
{ "label": "Build-Face-Recognition-DotNet", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/FaceRecognitionDotNet/AA.FaceRecognitionDotNet.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-People", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/People/AA.People.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-Shared", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/Shared/AA.Shared.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], "problemMatcher": "$msCompile" },
```

1
.vscode/read-me.md vendored Normal file
View File

@ -0,0 +1 @@
# Read Me

View File

@ -1,5 +1,6 @@
{
"cSpell.words": [
"accessibilities",
"appsettings",
"Argb",
"ASPNETCORE",
@ -8,6 +9,7 @@
"Bday",
"Beichler",
"Bohdi",
"CAXXXX",
"Cobertura",
"cref",
"CUDA",
@ -22,6 +24,7 @@
"Hmmssfff",
"jfif",
"JOSN",
"Makernote",
"mmod",
"Nicéphore",
"Niépce",
@ -29,6 +32,7 @@
"paramref",
"permille",
"permyriad",
"pged",
"Phares",
"Phgtv",
"Photoshop",
@ -36,6 +40,7 @@
"Rects",
"resnet",
"Serilog",
"snupkg",
"Subfile",
"Subfiles",
"Syncthing",

575
.vscode/tasks.json vendored
View File

@ -8,7 +8,7 @@
"args": [
"user-secrets",
"-p",
"${workspaceFolder}/Rename/AA.Rename.csproj",
"${workspaceFolder}/Windows/AA.Windows.csproj",
"init"
],
"problemMatcher": "$msCompile"
@ -20,19 +20,324 @@
"args": [
"user-secrets",
"-p",
"${workspaceFolder}/Rename/AA.Rename.csproj",
"${workspaceFolder}/Windows/AA.Windows.csproj",
"set",
"_Application",
"Rename"
"Windows"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format",
"label": "Build-Compare",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Compare/AA.Compare.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-Face",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Face/AA.Face.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Build-Face-Recognition-DotNet",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/FaceRecognitionDotNet/AA.FaceRecognitionDotNet.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-People",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/People/AA.People.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-Shared",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Shared/AA.Shared.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Build-Windows",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Windows/AA.Windows.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Pack-Compare",
"command": "dotnet",
"type": "process",
"args": [
"pack",
"${workspaceFolder}/Compare/AA.Compare.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Pack-Distance",
"command": "dotnet",
"type": "process",
"args": [
"pack",
"${workspaceFolder}/Distance/AA.Distance.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Pack-Face",
"command": "dotnet",
"type": "process",
"args": [
"pack",
"${workspaceFolder}/Face/AA.Face.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Pack-Face-Recognition-DotNet",
"command": "dotnet",
"type": "process",
"args": [
"pack",
"${workspaceFolder}/FaceRecognitionDotNet/AA.FaceRecognitionDotNet.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Pack-Metadata",
"command": "dotnet",
"type": "process",
"args": [
"pack",
"${workspaceFolder}/Metadata/AA.Metadata.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Pack-People",
"command": "dotnet",
"type": "process",
"args": [
"pack",
"${workspaceFolder}/People/AA.People.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Pack-Rename",
"command": "dotnet",
"type": "process",
"args": [
"pack",
"${workspaceFolder}/Rename/AA.Rename.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Pack-Shared",
"command": "dotnet",
"type": "process",
"args": [
"pack",
"${workspaceFolder}/Shared/AA.Shared.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Pack-Windows",
"command": "dotnet",
"type": "process",
"args": [
"pack",
"${workspaceFolder}/Windows/AA.Windows.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Compare-Whitespaces",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/Compare/AA.Compare.csproj",
"whitespace"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Distance-Whitespaces",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/Distance/AA.Distance.csproj",
"whitespace"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Face-Whitespaces",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/Face/AA.Face.csproj",
"whitespace"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Face-Recognition-DotNet-Whitespaces",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/FaceRecognitionDotNet/AA.FaceRecognitionDotNet.csproj",
"whitespace"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Metadata-Whitespaces",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/Metadata/AA.Metadata.csproj",
"whitespace"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-People-Whitespaces",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/People/AA.People.csproj",
"whitespace"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Rename-Whitespaces",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/Rename/AA.Rename.csproj",
"whitespace"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Shared-Whitespaces",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/Shared/AA.Shared.csproj",
"whitespace"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Compare",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/Compare/AA.Compare.csproj",
"--report",
".vscode",
"--verbosity",
@ -43,19 +348,237 @@
"problemMatcher": "$msCompile"
},
{
"label": "build",
"label": "Format-Distance",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/AA.sln",
"format",
"${workspaceFolder}/Distance/AA.Distance.csproj",
"--report",
".vscode",
"--verbosity",
"detailed",
"--severity",
"warn"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Face",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/Face/AA.Face.csproj",
"--report",
".vscode",
"--verbosity",
"detailed",
"--severity",
"warn"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Face-Recognition-DotNet",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/FaceRecognitionDotNet/AA.FaceRecognitionDotNet.csproj",
"--report",
".vscode",
"--verbosity",
"detailed",
"--severity",
"warn"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Metadata",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/Metadata/AA.Metadata.csproj",
"--report",
".vscode",
"--verbosity",
"detailed",
"--severity",
"warn"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-People",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/People/AA.People.csproj",
"--report",
".vscode",
"--verbosity",
"detailed",
"--severity",
"warn"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Rename",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/Rename/AA.Rename.csproj",
"--report",
".vscode",
"--verbosity",
"detailed",
"--severity",
"warn"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Shared",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/Shared/AA.Shared.csproj",
"--report",
".vscode",
"--verbosity",
"detailed",
"--severity",
"warn"
],
"problemMatcher": "$msCompile"
},
{
"label": "Format-Windows",
"command": "dotnet",
"type": "process",
"args": [
"format",
"${workspaceFolder}/Windows/AA.Windows.csproj",
"--report",
".vscode",
"--verbosity",
"detailed",
"--severity",
"warn"
],
"problemMatcher": "$msCompile"
},
{
"label": "AOT-Compare",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"-r",
"win-x64",
"-c",
"Release",
"-p:PublishAot=true",
"${workspaceFolder}/Compare/AA.Compare.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Publish AOT",
"label": "AOT-Distance",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"-r",
"win-x64",
"-c",
"Release",
"-p:PublishAot=true",
"${workspaceFolder}/Distance/AA.Distance.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "AOT-Face",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"-r",
"win-x64",
"-c",
"Release",
"-p:PublishAot=true",
"${workspaceFolder}/Face/AA.Face.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "AOT-Face-Recognition-DotNet",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"-r",
"win-x64",
"-c",
"Release",
"-p:PublishAot=true",
"${workspaceFolder}/FaceRecognitionDotNet/AA.FaceRecognitionDotNet.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "AOT-Metadata",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"-r",
"win-x64",
"-c",
"Release",
"-p:PublishAot=true",
"${workspaceFolder}/Metadata/AA.Metadata.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "AOT-People",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"-r",
"win-x64",
"-c",
"Release",
"-p:PublishAot=true",
"${workspaceFolder}/People/AA.People.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "AOT-Rename",
"command": "dotnet",
"type": "process",
"args": [
@ -70,6 +593,40 @@
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
},
{
"label": "AOT-Shared",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"-r",
"win-x64",
"-c",
"Release",
"-p:PublishAot=true",
"${workspaceFolder}/Shared/AA.Shared.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "AOT-Windows",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"-r",
"win-x64",
"-c",
"Release",
"-p:PublishAot=true",
"${workspaceFolder}/Windows/AA.Windows.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
]
}

34
AA.sln
View File

@ -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

1
Compare/.vscode/read-me.md vendored Normal file
View File

@ -0,0 +1 @@
# Read Me

55
Compare/AA.Compare.csproj Normal file
View File

@ -0,0 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<TargetFramework>net9.0</TargetFramework>
<UserSecretsId>770b6ae3-266e-4d5f-970a-173709b064de</UserSecretsId>
</PropertyGroup>
<PropertyGroup>
<PackageId>Phares.View.by.Distance.Compare</PackageId>
<Version>8.0.112.0</Version>
<Company>Phares</Company>
<Authors>Mike Phares</Authors>
<IncludeSymbols>true</IncludeSymbols>
<PackageReadmeFile>read-me.md</PackageReadmeFile>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>Windows</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)'=='true'">
<DefineConstants>OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>Linux</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<None Include=".vscode\read-me.md" Pack="true" PackagePath="\"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="runtime.win-x64.Microsoft.DotNet.ILCompiler" Version="8.0.12" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="ShellProgressBar" Version="5.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Face\AA.Face.csproj" />
<ProjectReference Include="..\Distance\AA.Distance.csproj" />
<ProjectReference Include="..\Metadata\AA.Metadata.csproj" />
<ProjectReference Include="..\People\AA.People.csproj" />
<ProjectReference Include="..\Shared\AA.Shared.csproj" />
</ItemGroup>
</Project>

168
Compare/Compare.cs Normal file
View File

@ -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;
using View_by_Distance.Face.Models.Stateless;
using View_by_Distance.Metadata.Models;
using View_by_Distance.People.Models.Stateless;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Stateless;
namespace View_by_Distance.Compare;
public partial class Compare : ICompare, IDisposable
{
private ProgressBar? _ProgressBar;
private readonly ProgressBarOptions _ProgressBarOptions;
public Compare(List<string> args, ILogger<Program>? 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<PersonContainer> personContainers = IPeople.GetPersonContainers(appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.PeopleSettings, appSettings.CompareSettings);
ReadOnlyCollection<long> personKeys = IPeople.GetPersonKeys(personContainers);
result = IPeople.GetReadOnlyCollections(appSettings.ResultSettings, appSettings.PeopleSettings, appSettings.DistanceSettings, appSettings.CompareSettings, personContainers, personKeys);
return result;
}
private static ReadOnlyCollection<ExifDirectory> GetMappedExifDirectoryWithEncoding(AppSettings appSettings, ICompare compare, long ticks, ReadOnlyCollections readOnlyCollections)
{
ReadOnlyCollection<ExifDirectory> results;
ReadOnlyCollection<ExifDirectory> 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<Program>? logger, AppSettings appSettings, ICompare compare, long ticks)
{
const int updated = 0;
DistanceLimits? distanceLimits;
logger?.LogInformation("{Ticks}", ticks);
ReadOnlyCollection<LocationContainer> matrix;
ReadOnlyCollection<SaveContainer> saveContainers;
ReadOnlyCollection<ExifDirectory> exifDirectories;
ReadOnlyCollection<LocationContainer> preFiltered;
ReadOnlyCollection<LocationContainer> postFiltered;
ReadOnlyDictionary<string, LocationContainer> onlyOne;
bool runToDoCollectionFirst = GetRunToDoCollectionFirst(appSettings, ticks);
ReadOnlyCollections readOnlyCollections = GetReadOnlyCollections(appSettings);
ReadOnlyCollection<ExifDirectory> mappedExifDirectoryWithEncoding = GetMappedExifDirectoryWithEncoding(appSettings, compare, ticks, readOnlyCollections);
ReadOnlyDictionary<int, ReadOnlyDictionary<int, FilePath>> 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);
}
}
}

View File

@ -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<ResultSettings>();
MetadataSettings? metadataSettings = configurationRoot.GetSection(nameof(MetadataSettings)).Get<MetadataSettings>();
PeopleSettings? peopleSettings = configurationRoot.GetSection(nameof(PeopleSettings)).Get<PeopleSettings>();
DistanceSettings? distanceSettings = configurationRoot.GetSection(nameof(DistanceSettings)).Get<DistanceSettings>();
CompareSettings? compareSettings = configurationRoot.GetSection(nameof(CompareSettings)).Get<CompareSettings>();
#pragma warning restore IL3050, IL2026
if (resultSettings is null || metadataSettings is null || peopleSettings is null || distanceSettings is null || compareSettings?.Company is null)
{
List<string> 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
{
}

View File

@ -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
{
}

View File

@ -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
{
}

53
Compare/Program.cs Normal file
View File

@ -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<Program> logger, List<string> args)
{
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddUserSecrets<Program>();
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<Program>? logger = Host.CreateDefaultBuilder(args).Build().Services.GetRequiredService<ILogger<Program>>();
#pragma warning restore IL3050
if (args is not null)
Secondary(logger, args.ToList());
else
Secondary(logger, []);
}
}

285
Distance/.editorconfig Normal file
View File

@ -0,0 +1,285 @@
[*.md]
end_of_line = crlf
file_header_template = unset
indent_size = 2
indent_style = space
insert_final_newline = false
root = true
tab_width = 2
[*.cs]
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
csharp_prefer_braces = false
csharp_prefer_qualified_reference = true:error
csharp_prefer_simple_default_expression = true:warning
csharp_prefer_simple_using_statement = true:warning
csharp_prefer_static_local_function = true:warning
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
csharp_style_allow_embedded_statements_on_same_line_experimental = true
csharp_style_conditional_delegate_call = true
csharp_style_deconstructed_variable_declaration = false
csharp_style_expression_bodied_accessors = when_on_single_line:warning
csharp_style_expression_bodied_constructors = when_on_single_line:warning
csharp_style_expression_bodied_indexers = when_on_single_line:warning
csharp_style_expression_bodied_lambdas = when_on_single_line:warning
csharp_style_expression_bodied_local_functions = when_on_single_line:warning
csharp_style_expression_bodied_methods = when_on_single_line:warning
csharp_style_expression_bodied_operators = when_on_single_line:warning
csharp_style_expression_bodied_properties = when_on_single_line:warning
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
csharp_style_inlined_variable_declaration = false
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_pattern_local_over_anonymous_function = true:warning
csharp_style_pattern_matching_over_as_with_null_check = true:warning
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
csharp_style_prefer_index_operator = true:warning
csharp_style_prefer_not_pattern = true:warning
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_pattern_matching = true:warning
csharp_style_prefer_range_operator = true:warning
csharp_style_prefer_switch_expression = true:warning
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable:warning
csharp_style_unused_value_expression_statement_preference = discard_variable:warning
csharp_style_var_elsewhere = false:warning
csharp_style_var_for_built_in_types = false:warning
csharp_style_var_when_type_is_apparent = false:warning
csharp_using_directive_placement = outside_namespace
dotnet_analyzer_diagnostic.category-Design.severity = error
dotnet_analyzer_diagnostic.category-Documentation.severity = error
dotnet_analyzer_diagnostic.category-Globalization.severity = none
dotnet_analyzer_diagnostic.category-Interoperability.severity = error
dotnet_analyzer_diagnostic.category-Maintainability.severity = error
dotnet_analyzer_diagnostic.category-Naming.severity = none
dotnet_analyzer_diagnostic.category-Performance.severity = none
dotnet_analyzer_diagnostic.category-Reliability.severity = error
dotnet_analyzer_diagnostic.category-Security.severity = error
dotnet_analyzer_diagnostic.category-SingleFile.severity = error
dotnet_analyzer_diagnostic.category-Style.severity = error
dotnet_analyzer_diagnostic.category-Usage.severity = error
dotnet_code_quality_unused_parameters = all
dotnet_code_quality_unused_parameters = non_public # IDE0060: Remove unused parameter
dotnet_code_quality.CAXXXX.api_surface = private, internal
dotnet_diagnostic.CA1001.severity = error # CA1001: Types that own disposable fields should be disposable
dotnet_diagnostic.CA1051.severity = error # CA1051: Do not declare visible instance fields
dotnet_diagnostic.CA1825.severity = warning # CA1823: Avoid zero-length array allocations
dotnet_diagnostic.CA1829.severity = warning # CA1829: Use Length/Count property instead of Count() when available
dotnet_diagnostic.CA1834.severity = warning # CA1834: Consider using 'StringBuilder.Append(char)' when applicable
dotnet_diagnostic.CA1860.severity = error # CA1860: Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance
dotnet_diagnostic.CA1861.severity = error # CA1861: Prefer 'static readonly' fields over constant array arguments
dotnet_diagnostic.CA1869.severity = error # CA1869: Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead.
dotnet_diagnostic.CA2201.severity = none # CA2201: Exception type System.NullReferenceException is reserved by the runtime
dotnet_diagnostic.CA2254.severity = error # CA2254: The logging message template should not vary between calls to 'LoggerExtensions.LogInformation(ILogger, string?, params object?[])'
dotnet_diagnostic.IDE0001.severity = warning # IDE0001: Simplify name
dotnet_diagnostic.IDE0002.severity = warning # Simplify (member access) - System.Version.Equals("1", "2"); Version.Equals("1", "2");
dotnet_diagnostic.IDE0004.severity = warning # IDE0004: Cast is redundant.
dotnet_diagnostic.IDE0005.severity = warning # Using directive is unnecessary
dotnet_diagnostic.IDE0010.severity = error # Add missing cases to switch statement (IDE0010)
dotnet_diagnostic.IDE0028.severity = error # IDE0028: Collection initialization can be simplified
dotnet_diagnostic.IDE0031.severity = warning # Use null propagation (IDE0031)
dotnet_diagnostic.IDE0047.severity = warning # IDE0047: Parentheses can be removed
dotnet_diagnostic.IDE0048.severity = error # Parentheses preferences (IDE0047 and IDE0048)
dotnet_diagnostic.IDE0049.severity = warning # Use language keywords instead of framework type names for type references (IDE0049)
dotnet_diagnostic.IDE0051.severity = error # Private member '' is unused [, ]
dotnet_diagnostic.IDE0060.severity = warning # IDE0060: Remove unused parameter
dotnet_diagnostic.IDE0130.severity = none # Namespace does not match folder structure (IDE0130)
dotnet_diagnostic.IDE0230.severity = warning # IDE0230: Use UTF-8 string literal
dotnet_diagnostic.IDE0290.severity = error # Use primary constructor [Distance]csharp(IDE0290)
dotnet_diagnostic.IDE0300.severity = error # IDE0300: Collection initialization can be simplified
dotnet_diagnostic.IDE0301.severity = error #IDE0301: Collection initialization can be simplified
dotnet_diagnostic.IDE0305.severity = none # IDE0305: Collection initialization can be simplified
dotnet_diagnostic.JSON002.severity = warning # JSON002: Probable JSON string detected
dotnet_naming_rule.abstract_method_should_be_pascal_case.severity = warning
dotnet_naming_rule.abstract_method_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.abstract_method_should_be_pascal_case.symbols = abstract_method
dotnet_naming_rule.class_should_be_pascal_case.severity = warning
dotnet_naming_rule.class_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.class_should_be_pascal_case.symbols = class
dotnet_naming_rule.delegate_should_be_pascal_case.severity = warning
dotnet_naming_rule.delegate_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.delegate_should_be_pascal_case.symbols = delegate
dotnet_naming_rule.enum_should_be_pascal_case.severity = warning
dotnet_naming_rule.enum_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.enum_should_be_pascal_case.symbols = enum
dotnet_naming_rule.event_should_be_pascal_case.severity = warning
dotnet_naming_rule.event_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.event_should_be_pascal_case.symbols = event
dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.method_should_be_pascal_case.severity = warning
dotnet_naming_rule.method_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.method_should_be_pascal_case.symbols = method
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.private_method_should_be_pascal_case.severity = warning
dotnet_naming_rule.private_method_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.private_method_should_be_pascal_case.symbols = private_method
dotnet_naming_rule.private_or_internal_field_should_be_private_of_internal_field.severity = warning
dotnet_naming_rule.private_or_internal_field_should_be_private_of_internal_field.style = private_of_internal_field
dotnet_naming_rule.private_or_internal_field_should_be_private_of_internal_field.symbols = private_or_internal_field
dotnet_naming_rule.private_or_internal_static_field_should_be_private_of_internal_field.severity = warning
dotnet_naming_rule.private_or_internal_static_field_should_be_private_of_internal_field.style = private_of_internal_field
dotnet_naming_rule.private_or_internal_static_field_should_be_private_of_internal_field.symbols = private_or_internal_static_field
dotnet_naming_rule.property_should_be_pascal_case.severity = warning
dotnet_naming_rule.property_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.property_should_be_pascal_case.symbols = property
dotnet_naming_rule.public_or_protected_field_should_be_private_of_internal_field.severity = warning
dotnet_naming_rule.public_or_protected_field_should_be_private_of_internal_field.style = private_of_internal_field
dotnet_naming_rule.public_or_protected_field_should_be_private_of_internal_field.symbols = public_or_protected_field
dotnet_naming_rule.static_field_should_be_pascal_case.severity = warning
dotnet_naming_rule.static_field_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.static_field_should_be_pascal_case.symbols = static_field
dotnet_naming_rule.static_method_should_be_pascal_case.severity = warning
dotnet_naming_rule.static_method_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.static_method_should_be_pascal_case.symbols = static_method
dotnet_naming_rule.struct_should_be_pascal_case.severity = warning
dotnet_naming_rule.struct_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.struct_should_be_pascal_case.symbols = struct
dotnet_naming_rule.types_should_be_pascal_case.severity = warning
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.private_of_internal_field.capitalization = pascal_case
dotnet_naming_style.private_of_internal_field.required_prefix = _
dotnet_naming_style.private_of_internal_field.required_suffix =
dotnet_naming_style.private_of_internal_field.word_separator =
dotnet_naming_symbols.abstract_method.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.abstract_method.applicable_kinds = method
dotnet_naming_symbols.abstract_method.required_modifiers = abstract
dotnet_naming_symbols.class.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.class.applicable_kinds = class
dotnet_naming_symbols.class.required_modifiers =
dotnet_naming_symbols.delegate.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.delegate.applicable_kinds = delegate
dotnet_naming_symbols.delegate.required_modifiers =
dotnet_naming_symbols.enum.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.enum.applicable_kinds = enum
dotnet_naming_symbols.enum.required_modifiers =
dotnet_naming_symbols.event.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.event.applicable_kinds = event
dotnet_naming_symbols.event.required_modifiers =
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.method.applicable_accessibilities = public
dotnet_naming_symbols.method.applicable_kinds = method
dotnet_naming_symbols.method.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.required_modifiers =
dotnet_naming_symbols.private_method.applicable_accessibilities = private
dotnet_naming_symbols.private_method.applicable_kinds = method
dotnet_naming_symbols.private_method.required_modifiers =
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_field.required_modifiers =
dotnet_naming_symbols.private_or_internal_static_field.applicable_accessibilities = internal, private, private_protected
dotnet_naming_symbols.private_or_internal_static_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_static_field.required_modifiers = static
dotnet_naming_symbols.property.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.property.applicable_kinds = property
dotnet_naming_symbols.property.required_modifiers =
dotnet_naming_symbols.public_or_protected_field.applicable_accessibilities = public, protected
dotnet_naming_symbols.public_or_protected_field.applicable_kinds = field
dotnet_naming_symbols.public_or_protected_field.required_modifiers =
dotnet_naming_symbols.static_field.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.static_field.applicable_kinds = field
dotnet_naming_symbols.static_field.required_modifiers = static
dotnet_naming_symbols.static_method.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.static_method.applicable_kinds = method
dotnet_naming_symbols.static_method.required_modifiers = static
dotnet_naming_symbols.struct.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.struct.applicable_kinds = struct
dotnet_naming_symbols.struct.required_modifiers =
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.required_modifiers =
dotnet_remove_unnecessary_suppression_exclusions = 0
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
dotnet_style_allow_multiple_blank_lines_experimental = false:warning
dotnet_style_allow_statement_immediately_after_block_experimental = true
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true:warning
dotnet_style_explicit_tuple_names = true:warning
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true:warning
dotnet_style_object_initializer = true:warning
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true:warning
dotnet_style_prefer_auto_properties = true:warning
dotnet_style_prefer_compound_assignment = true:warning
dotnet_style_prefer_conditional_expression_over_assignment = false
dotnet_style_prefer_conditional_expression_over_return = false
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
dotnet_style_prefer_inferred_tuple_names = true:warning
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
dotnet_style_prefer_simplified_boolean_expressions = true:warning
dotnet_style_prefer_simplified_interpolation = true
dotnet_style_qualification_for_event = false:error
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false:error
dotnet_style_qualification_for_property = false:error
dotnet_style_readonly_field = true:warning
dotnet_style_require_accessibility_modifiers = for_non_interface_members
end_of_line = crlf
file_header_template = unset
indent_size = 4
indent_style = space
insert_final_newline = false
root = true
tab_width = 4
# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1822
# https://github.com/dotnet/aspnetcore/blob/main/.editorconfig
# https://github.com/dotnet/project-system/blob/main/.editorconfig

1
Distance/.vscode/read-me.md vendored Normal file
View File

@ -0,0 +1 @@
# Read Me

View File

@ -0,0 +1,51 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>library</OutputType>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<PackageId>Phares.AA.Distance</PackageId>
<Version>8.0.112.0</Version>
<Company>Phares</Company>
<Authors>Mike Phares</Authors>
<IncludeSymbols>true</IncludeSymbols>
<PackageReadmeFile>read-me.md</PackageReadmeFile>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>Windows</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)'=='true'">
<DefineConstants>OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>Linux</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<None Include=".vscode\read-me.md" Pack="true" PackagePath="\"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="MetadataExtractor" Version="2.8.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="WindowsShortcutFactory" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FaceRecognitionDotNet\AA.FaceRecognitionDotNet.csproj" />
<ProjectReference Include="..\Metadata\AA.Metadata.csproj" />
<ProjectReference Include="..\Shared\AA.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,3 @@
namespace View_by_Distance.Metadata.Models;
public class C_Resize() { }

View File

@ -0,0 +1,3 @@
namespace View_by_Distance.Metadata.Models;
public class D2_FaceParts() { }

View File

@ -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;
}
}

View File

@ -0,0 +1,3 @@
namespace View_by_Distance.Metadata.Models;
public class E_Distance() { }

View File

@ -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);
}

View File

@ -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;
namespace View_by_Distance.Distance.Models.Stateless;
internal static class FaceEncodingLogic
{
internal static ReadOnlyCollection<ExifDirectory> GetMappedExifDirectoryWithEncoding(ICompare compare, long ticks, ReadOnlyCollection<ExifDirectory> exifDirectories)
{
List<ExifDirectory> results = [];
string? json;
FaceEncoding? faceEncoding;
ExifDirectory exifDirectory;
FaceRecognitionDotNet.Models.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.IMetadata.GetFaceEncoding(e);
faceEncoding = json is null ? null : JsonSerializer.Deserialize(json, FaceEncodingGenerationContext.Default.FaceEncoding);
if (faceEncoding is null)
continue;
encoding = FaceRecognitionDotNet.Models.FaceRecognition.LoadFaceEncoding(faceEncoding.RawEncoding);
exifDirectory = ExifDirectory.Get(encoding, e);
results.Add(exifDirectory);
}
return results.AsReadOnly();
}
}

View File

@ -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;
namespace View_by_Distance.Distance.Models.Stateless;
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<LocationContainer> GetPreFilterLocationContainer(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollections readOnlyCollections, ReadOnlyDictionary<int, ReadOnlyDictionary<int, FilePath>> keyValuePairs, ReadOnlyCollection<ExifDirectory> exifDirectories)
{
List<LocationContainer> results = [];
string? json;
string? model;
bool? canReMap;
bool? isFocusPerson;
bool? inSkipCollection;
FaceEncoding? faceEncoding;
ReadOnlyDictionary<int, FilePath>? keyValues;
FaceRecognitionDotNet.Models.FaceEncoding? encoding;
List<FilePathAndWholePercentages>? wholePercentagesCollection;
ReadOnlyCollection<LocationContainer> 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.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.IMetadata.GetFaceEncoding(locationContainer.ExifDirectory);
faceEncoding = json is null ? null : JsonSerializer.Deserialize(json, FaceEncodingGenerationContext.Default.FaceEncoding);
if (faceEncoding is null)
continue;
encoding = FaceRecognitionDotNet.Models.FaceRecognition.LoadFaceEncoding(faceEncoding.RawEncoding);
results.Add(LocationContainer.Get(locationContainer, encoding, keepExifDirectory: false));
}
return results.AsReadOnly();
}
}

View File

@ -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;
namespace View_by_Distance.Distance.Models.Stateless;
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<LocationContainer> 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.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.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<LocationContainer> GetLocationContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollection<ExifDirectory> exifDirectories, string sourceClass)
{
List<LocationContainer> 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();
}
}

View File

@ -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;
namespace View_by_Distance.Distance.Models.Stateless;
internal static class FilterLogicC
{
internal static ReadOnlyCollection<LocationContainer> GetPostFilterLocationContainer(ReadOnlyCollection<LocationContainer> preFiltered, DistanceLimits distanceLimits)
{
List<LocationContainer> 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<LocationContainer> GetCombined(DistanceSettings distanceSettings, Shared.Models.Properties.ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollection<ExifDirectory> exifDirectories, ReadOnlyCollection<LocationContainer> postFiltered)
{
List<LocationContainer> results = [];
foreach (LocationContainer locationContainer in postFiltered)
results.Add(locationContainer);
ReadOnlyCollection<LocationContainer> locationContainers = FilterLogicB.GetLocationContainers(distanceSettings, compareSettings, compare, ticks, exifDirectories, nameof(FilterLogicC));
foreach (LocationContainer locationContainer in locationContainers)
results.Add(locationContainer);
return results.AsReadOnly();
}
internal static ReadOnlyCollection<LocationContainer> GetMatrixLocationContainers(DistanceSettings distanceSettings, Shared.Models.Properties.ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollection<ExifDirectory> mappedExifDirectoryWithEncoding, DistanceLimits distanceLimits, ReadOnlyCollection<LocationContainer> postFiltered)
{
List<LocationContainer> results = [];
ReadOnlyCollection<LocationContainer> collection;
ReadOnlyCollection<LocationContainer> 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.Models.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<string, LocationContainer> GetOnlyOne(DistanceSettings distanceSettings, ReadOnlyCollection<LocationContainer> matrix)
{
Dictionary<string, LocationContainer> results = [];
List<string> 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();
}
}

View File

@ -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;
using WindowsShortcutFactory;
namespace View_by_Distance.Distance.Models.Stateless;
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(IMapLogic.Mapping)}Null";
}
else
{
isByMapping = by == IMapLogic.Mapping;
isBySorting = by == IMapLogic.Sorting;
bool isDefaultName = displayDirectoryName is not null && IPerson.IsDefaultName(displayDirectoryName);
if (isBySorting && displayDirectoryName is null)
byValue = saveIndividually ? nameof(IMapLogic.Individually) : $"{nameof(IMapLogic.Sorting)} Without Person{(distancePermyriad < 2000 ? "-A" : "-Z")}";
else if (isBySorting && useFiltersCounter.HasValue)
byValue = $"{nameof(IMapLogic.Sorting)}{(!isDefaultName ? "-A" : "-Z")} Modified Filters - {useFiltersCounter.Value}";
else
{
byValue = $"{by.Value switch
{
IMapLogic.Mapping => nameof(IMapLogic.Mapping),
IMapLogic.Sorting => saveIndividually ? nameof(IMapLogic.Individually) : nameof(IMapLogic.Sorting),
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 : 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<SaveContainer> GetSaveContainers(ResultSettings resultSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, string outputResolution, ReadOnlyDictionary<string, LocationContainer> onlyOne)
{
List<SaveContainer> 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(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<string, LocationContainer> 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<SaveContainer> saveContainers)
{
string fileName;
string checkFile;
string sourceFile;
List<string> 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)
{ }
}
}
}

View File

@ -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;
namespace View_by_Distance.Distance.Models.Stateless;
public interface IDistance
{
static ReadOnlyCollection<ExifDirectory> 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<ExifDirectory> 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<int, ReadOnlyDictionary<int, FilePath>> TestStatic_Extract(ICompareSettings compareSettings, ReadOnlyCollection<ExifDirectory> exifDirectories) =>
Extract(compareSettings, exifDirectories);
static ReadOnlyDictionary<int, ReadOnlyDictionary<int, FilePath>> Extract(ICompareSettings compareSettings, ReadOnlyCollection<ExifDirectory> exifDirectories) =>
MappedLogicA.Extract(compareSettings, exifDirectories);
static ReadOnlyCollection<ExifDirectory> TestStatic_GetMappedExifDirectoryWithEncoding(ICompare compare, long ticks, ReadOnlyCollection<ExifDirectory> exifDirectories) =>
GetMappedExifDirectoryWithEncoding(compare, ticks, exifDirectories);
static ReadOnlyCollection<ExifDirectory> GetMappedExifDirectoryWithEncoding(ICompare compare, long ticks, ReadOnlyCollection<ExifDirectory> exifDirectories) =>
FaceEncodingLogic.GetMappedExifDirectoryWithEncoding(compare, ticks, exifDirectories);
static ReadOnlyCollection<LocationContainer> TestStatic_GetPreFilterLocationContainer(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollections readOnlyCollections, ReadOnlyDictionary<int, ReadOnlyDictionary<int, FilePath>> keyValuePairs, ReadOnlyCollection<ExifDirectory> exifDirectories) =>
GetPreFilterLocationContainer(distanceSettings, compareSettings, compare, ticks, readOnlyCollections, keyValuePairs, exifDirectories);
static ReadOnlyCollection<LocationContainer> GetPreFilterLocationContainer(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollections readOnlyCollections, ReadOnlyDictionary<int, ReadOnlyDictionary<int, FilePath>> keyValuePairs, ReadOnlyCollection<ExifDirectory> exifDirectories) =>
FilterLogicA.GetPreFilterLocationContainer(distanceSettings, compareSettings, compare, ticks, readOnlyCollections, keyValuePairs, exifDirectories);
static ReadOnlyCollection<LocationContainer> TestStatic_GetPostFilterLocationContainer(ReadOnlyCollection<LocationContainer> preFiltered, DistanceLimits distanceLimits) =>
GetPostFilterLocationContainer(preFiltered, distanceLimits);
static ReadOnlyCollection<LocationContainer> GetPostFilterLocationContainer(ReadOnlyCollection<LocationContainer> preFiltered, DistanceLimits distanceLimits) =>
FilterLogicC.GetPostFilterLocationContainer(preFiltered, distanceLimits);
static ReadOnlyCollection<LocationContainer> TestStatic_GetMatrixLocationContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollection<ExifDirectory> mappedExifDirectoryWithEncoding, DistanceLimits distanceLimits, ReadOnlyCollection<LocationContainer> postFiltered) =>
GetMatrixLocationContainers(distanceSettings, compareSettings, compare, ticks, mappedExifDirectoryWithEncoding, distanceLimits, postFiltered);
static ReadOnlyCollection<LocationContainer> GetMatrixLocationContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollection<ExifDirectory> mappedExifDirectoryWithEncoding, DistanceLimits distanceLimits, ReadOnlyCollection<LocationContainer> postFiltered) =>
FilterLogicC.GetMatrixLocationContainers(distanceSettings, compareSettings, compare, ticks, mappedExifDirectoryWithEncoding, distanceLimits, postFiltered);
static ReadOnlyDictionary<string, LocationContainer> TestStatic_GetOnlyOne(DistanceSettings distanceSettings, ReadOnlyCollection<LocationContainer> matrix) =>
GetOnlyOne(distanceSettings, matrix);
static ReadOnlyDictionary<string, LocationContainer> GetOnlyOne(DistanceSettings distanceSettings, ReadOnlyCollection<LocationContainer> matrix) =>
FilterLogicC.GetOnlyOne(distanceSettings, matrix);
static ReadOnlyCollection<SaveContainer> TestStatic_GetSaveContainers(ResultSettings resultSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, string outputResolution, ReadOnlyDictionary<string, LocationContainer> onlyOne) =>
GetSaveContainers(resultSettings, distanceSettings, compareSettings, compare, ticks, outputResolution, onlyOne);
static ReadOnlyCollection<SaveContainer> GetSaveContainers(ResultSettings resultSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, string outputResolution, ReadOnlyDictionary<string, LocationContainer> 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<SaveContainer> saveContainers) =>
SaveContainers(distanceSettings, compareSettings, compare, ticks, updated, saveContainers);
static void SaveContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, int? updated, ReadOnlyCollection<SaveContainer> saveContainers) =>
FilterLogicD.SaveContainers(distanceSettings, compareSettings, compare, ticks, updated, saveContainers);
}

View File

@ -0,0 +1,204 @@
using System.Collections.ObjectModel;
using View_by_Distance.Metadata.Models;
using View_by_Distance.Metadata.Models.Stateless;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Properties;
using View_by_Distance.Shared.Models.Stateless;
namespace View_by_Distance.Distance.Models.Stateless;
internal static class MappedLogicA
{
internal record MappedFile(PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName,
string? PersonDisplayDirectoryName,
FilePath FilePath);
private static List<MappedFile> GetDisplayDirectoryAllFiles(PeopleSettings peopleSettings, ICompareSettings compareSettings, ReadOnlyCollections readOnlyCollections)
{
List<MappedFile> results = [];
FilePath filePath;
MappedFile mappedFile;
string personKeyFormatted;
List<string> 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--)
{
filePath = personContainer.DisplayDirectoryAllFilePaths[i];
if (filePath.ExtensionLowered != compareSettings.FacesFileNameExtension)
continue;
if (distinct.Contains(filePath.Name))
continue;
distinct.Add(filePath.Name);
personKeyFormatted = IPersonBirthday.GetFormatted(peopleSettings.PersonBirthdayFormat, personContainer.Key.Value);
personKeyFormattedAndKeyTicksAndDisplayDirectoryName = new(personKeyFormatted, personContainer.Key.Value, filePath.Name);
mappedFile = new(personKeyFormattedAndKeyTicksAndDisplayDirectoryName, personContainer.DisplayDirectoryName, filePath);
results.Add(mappedFile);
}
}
return results;
}
private static ReadOnlyCollection<MappedFile> GetMappedFiles(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, ICompareSettings compareSettings, ReadOnlyCollections readOnlyCollections, ReadOnlyCollection<MappedLogicB.Record> records)
{
List<MappedFile> results = [];
long personKey;
string checkFile;
FilePath filePath;
FileHolder fileHolder;
MappedFile mappedFile;
List<string> distinct = [];
PersonBirthday? personBirthday;
PersonContainer? personContainer;
string? personDisplayDirectoryName;
results.AddRange(GetDisplayDirectoryAllFiles(peopleSettings, compareSettings, readOnlyCollections));
PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName personKeyFormattedAndKeyTicksAndDisplayDirectoryName;
ReadOnlyDictionary<long, PersonContainer> keyValuePairs = PersonContainer.Extract(readOnlyCollections.PersonContainers);
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;
personKey = personBirthday.Value.Ticks;
distinct.Add(record.MappedFaceFilePath.Name);
if (!keyValuePairs.TryGetValue(personKey, out personContainer))
personDisplayDirectoryName = record.PersonDisplayDirectoryName;
else
personDisplayDirectoryName = personContainer.DisplayDirectoryName;
personKeyFormattedAndKeyTicksAndDisplayDirectoryName = new(record.PersonKeyFormatted, personKey, personDisplayDirectoryName);
mappedFile = new(personKeyFormattedAndKeyTicksAndDisplayDirectoryName, 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<ExifDirectory> exifDirectories, ReadOnlyDictionary<int, List<FilePathAndWholePercentages>> 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<FilePathAndWholePercentages>? 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;
Stream stream = File.OpenRead(mappedFile.FilePath.FullName);
ExifDirectory exifDirectory = IMetadata.GetExifDirectory(mappedFile.FilePath, stream, mappedFile.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName);
stream.Dispose();
lock (exifDirectories)
exifDirectories.Add(exifDirectory);
}
internal static ReadOnlyCollection<ExifDirectory> GetMapped(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, ReadOnlyCollections readOnlyCollections)
{
List<ExifDirectory> results = [];
string eDistanceContentDirectory = IResult.GetResultsDateGroupDirectory(resultSettings, nameof(E_Distance), resultSettings.ResultContent);
ReadOnlyCollection<MappedLogicB.Record> records = MappedLogicB.DeleteEmptyDirectoriesAndGetCollection(resultSettings, metadataSettings, peopleSettings, distanceSettings, compareSettings, compare, ticks, eDistanceContentDirectory, readOnlyCollections);
ReadOnlyCollection<MappedFile> 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<int, List<FilePathAndWholePercentages>> 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<int, ReadOnlyDictionary<int, FilePath>> Extract(ICompareSettings compareSettings, ReadOnlyCollection<ExifDirectory> exifDirectories)
{
Dictionary<int, ReadOnlyDictionary<int, FilePath>> results = [];
int? wholePercentages;
Dictionary<int, FilePath>? keyValues;
Dictionary<int, Dictionary<int, FilePath>> 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<int, Dictionary<int, FilePath>> keyValuePair in keyValuePairs)
results.Add(keyValuePair.Key, keyValuePair.Value.AsReadOnly());
return results.AsReadOnly();
}
}

View File

@ -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;
namespace View_by_Distance.Distance.Models.Stateless;
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<TicksDirectory> UpdateDateVerifyAndGetTicksDirectories(DistanceSettings distanceSettings, string eDistanceContentDirectory)
{
List<TicksDirectory> 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<Record> GetRecords(ResultSettings resultSettings, MetadataSettings metadataSettings, ICompareSettings compareSettings, bool? isDefault, string[] files, int directoryNumber, string personKeyFormatted, int? linksCount, List<string> distinct, string? personDisplayDirectoryName)
{
List<Record> 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<string> 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<Record> DeleteEmptyDirectoriesAndGetCollection(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, string eDistanceContentDirectory, ReadOnlyCollections readOnlyCollections)
{
List<Record> 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<string> distinct = [];
string? personFirstInitial;
bool isReservedDirectoryName;
string[] personNameDirectories;
string? newestPersonKeyFormatted;
string? personDisplayDirectoryName;
string[] personNameLinkDirectories;
string? personFirstInitialDirectory;
List<TicksDirectory> 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();
}
}

1
Face/.vscode/read-me.md vendored Normal file
View File

@ -0,0 +1 @@
# Read Me

49
Face/AA.Face.csproj Normal file
View File

@ -0,0 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>library</OutputType>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<PackageId>Phares.AA.Face</PackageId>
<Version>8.0.112.0</Version>
<Company>Phares</Company>
<Authors>Mike Phares</Authors>
<IncludeSymbols>true</IncludeSymbols>
<PackageReadmeFile>read-me.md</PackageReadmeFile>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>Windows</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)'=='true'">
<DefineConstants>OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>Linux</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<None Include=".vscode\read-me.md" Pack="true" PackagePath="\"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="MetadataExtractor" Version="2.8.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Metadata\AA.Metadata.csproj" />
<ProjectReference Include="..\Shared\AA.Shared.csproj" />
</ItemGroup>
</Project>

3
Face/Models/D_Face.cs Normal file
View File

@ -0,0 +1,3 @@
namespace View_by_Distance.Metadata.Models;
public class D_Face() { }

View File

@ -0,0 +1,55 @@
using System.Collections.ObjectModel;
using View_by_Distance.Metadata.Models;
using View_by_Distance.Metadata.Models.Stateless;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Properties;
using View_by_Distance.Shared.Models.Stateless;
namespace View_by_Distance.Face.Models.Stateless;
internal static class Face
{
private static void ExifDirectoriesParallelFor(ResultSettings resultSettings, MetadataSettings metadataSettings, List<ExifDirectory> results, FileInfo fileInfo)
{
FileHolder fileHolder = FileHolder.Get(fileInfo, id: null);
const PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName = null;
FilePath filePath = FilePath.Get(resultSettings, metadataSettings, fileHolder, index: null);
Stream stream = File.OpenRead(filePath.FullName);
ExifDirectory exifDirectory = IMetadata.GetExifDirectory(filePath, stream, personKeyFormattedAndKeyTicksAndDisplayDirectoryName);
stream.Dispose();
results.Add(exifDirectory);
}
internal static ReadOnlyCollection<ExifDirectory> GetExifDirectories(ResultSettings resultSettings, MetadataSettings metadataSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, string outputResolution)
{
List<ExifDirectory> 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();
}
}

View File

@ -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;
namespace View_by_Distance.Face.Models.Stateless;
public interface IFace
{
ReadOnlyCollection<ExifDirectory> 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<ExifDirectory> GetExifDirectories(ResultSettings resultSettings, MetadataSettings metadataSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, long ticks, string outputResolution) =>
Face.GetExifDirectories(resultSettings, metadataSettings, distanceSettings, compareSettings, compare, ticks, outputResolution);
}

View File

@ -0,0 +1 @@
# Read Me

View File

@ -0,0 +1,51 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>library</OutputType>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<PackageId>Phares.AA.FaceRecognitionDotNet</PackageId>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<Version>9.0.100.1</Version>
<Authors>Mike Phares</Authors>
<Company>Phares</Company>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>Windows</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)'=='true'">
<DefineConstants>OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>Linux</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DlibDotNet" Version="19.21.0.20220724" />
<!--PackageReference Include="configuration.MKL" Version="19.21.0.20210302" /-->
<!--PackageReference Include="DlibDotNet-WithCUDA" Version="19.17.0.20190429" /-->
<!--PackageReference Include="configuration.CUDA92" Version="19.21.0.20210302" /-->
<!--PackageReference Include="configuration.CUDA102" Version="19.21.0.20210302" /-->
<!--PackageReference Include="configuration.CUDA110" Version="19.21.0.20210302" /-->
<!--PackageReference Include="configuration.CUDA111" Version="19.21.0.20210302" /-->
<!--PackageReference Include="configuration.CUDA112" Version="19.21.0.20210302" /-->
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\AA.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,112 @@
using DlibDotNet;
using DlibDotNet.Dnn;
using View_by_Distance.FaceRecognitionDotNet.Models;
using View_by_Distance.Shared.Models.Stateless;
namespace View_by_Distance.FaceRecognitionDotNet.Dlib.Python;
internal sealed class CnnFaceDetectionModelV1
{
#region Methods
public static IEnumerable<MModRect> Detect(LossMmod net, Image image, int upsampleNumTimes)
{
using PyramidDown? pyr = new(2);
List<MModRect>? rects = [];
// Copy the data into dlib based objects
using Matrix<RgbPixel>? 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<PyramidDown>(matrix, 2);
}
OutputLabels<IEnumerable<MModRect>>? 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<IEnumerable<MModRect>> DetectMulti(LossMmod net, IEnumerable<Image> images, int upsampleNumTimes, int batchSize = 128)
{
List<Matrix<RgbPixel>>? destImages = [];
List<IEnumerable<MModRect>>? allRects = [];
try
{
using PyramidDown? pyr = new(2);
// Copy the data into dlib based objects
foreach (Image? image in images)
{
Matrix<RgbPixel>? 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<IEnumerable<MModRect>>? dets = net.Operator(destImages, (ulong)batchSize);
foreach (IEnumerable<MModRect>? det in dets)
{
List<MModRect>? 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<RgbPixel>? matrix in destImages)
matrix.Dispose();
}
return allRects;
}
#endregion
}

View File

@ -0,0 +1,130 @@
using DlibDotNet;
using DlibDotNet.Dnn;
using View_by_Distance.FaceRecognitionDotNet.Models;
namespace View_by_Distance.FaceRecognitionDotNet.Dlib.Python;
internal sealed class FaceRecognitionModelV1
{
#region Methods
public static Matrix<double> ComputeFaceDescriptor(LossMetric net, Image img, FullObjectDetection face, int numberOfJitters)
{
FullObjectDetection[]? faces = [face];
return ComputeFaceDescriptors(net, img, faces, numberOfJitters).First();
}
public static IEnumerable<Matrix<double>> ComputeFaceDescriptors(LossMetric net, Image img, IEnumerable<FullObjectDetection> faces, int numberOfJitters)
{
Image[]? batchImage = [img];
IEnumerable<FullObjectDetection>[]? batchFaces = [faces];
return BatchComputeFaceDescriptors(net, batchImage, batchFaces, numberOfJitters).First();
}
public static IEnumerable<IEnumerable<Matrix<double>>> BatchComputeFaceDescriptors(LossMetric net,
IList<Image> batchImages,
IList<IEnumerable<FullObjectDetection>> 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<FullObjectDetection>? 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<Array<Matrix<RgbPixel>>>? faceChipsArray = new(batchImages.Count);
List<Matrix<RgbPixel>>? faceChips = [];
for (int i = 0; i < batchImages.Count; ++i)
{
IEnumerable<FullObjectDetection>? faces = batchFaces[i];
Image? img = batchImages[i];
List<ChipDetails>? dets = new(faces.Count());
foreach (FullObjectDetection? f in faces)
dets.Add(DlibDotNet.Dlib.GetFaceChipDetails(f, 150, 0.25));
Array<Matrix<RgbPixel>>? thisImageFaceChips = DlibDotNet.Dlib.ExtractImageChips<RgbPixel>(img.Matrix, dets);
foreach (Matrix<RgbPixel>? chip in thisImageFaceChips)
faceChips.Add(chip);
faceChipsArray.Add(thisImageFaceChips);
foreach (ChipDetails? det in dets)
det.Dispose();
}
List<List<Matrix<double>>>? 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<Matrix<float>>? descriptors = net.Operator(faceChips, 16);
int index = 0;
Matrix<float>[]? 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<double>(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<RgbPixel>[]? tmp = JitterImage(faceChips[index++], numberOfJitters).ToArray();
using (OutputLabels<Matrix<float>>? tmp2 = net.Operator(tmp, 16))
using (MatrixOp? mat = DlibDotNet.Dlib.Mat(tmp2))
{
Matrix<double>? r = DlibDotNet.Dlib.Mean<double>(mat);
faceDescriptors[i].Add(r);
}
foreach (Matrix<RgbPixel>? matrix in tmp)
matrix.Dispose();
}
if (index != faceChips.Count)
throw new ApplicationException();
}
if (faceChipsArray.Count > 0)
{
foreach (Array<Matrix<RgbPixel>>? array in faceChipsArray)
{
foreach (Matrix<RgbPixel>? faceChip in array)
faceChip.Dispose();
array.Dispose();
}
}
return faceDescriptors;
}
#region Helpers
private static readonly Rand _Rand = new();
private static IEnumerable<Matrix<RgbPixel>> JitterImage(Matrix<RgbPixel> img, int numberOfJitters)
{
List<Matrix<RgbPixel>>? crops = [];
for (int i = 0; i < numberOfJitters; ++i)
crops.Add(DlibDotNet.Dlib.JitterImage(img, _Rand));
return crops;
}
#endregion
#endregion
}

View File

@ -0,0 +1,170 @@
using DlibDotNet;
using View_by_Distance.FaceRecognitionDotNet.Models;
using View_by_Distance.Shared.Models.Stateless;
namespace View_by_Distance.FaceRecognitionDotNet.Dlib.Python;
internal sealed class SimpleObjectDetector
{
#region Methods
public static IEnumerable<Rectangle> RunDetectorWithUpscale1(FrontalFaceDetector detector,
Image img,
uint upsamplingAmount,
double adjustThreshold,
List<double> detectionConfidences,
List<ulong> weightIndices)
{
List<Rectangle>? rectangles = [];
if (img.Mode == Mode.Greyscale)
{
Matrix<byte>? greyscaleMatrix = img.Matrix as Matrix<byte>;
if (upsamplingAmount == 0)
{
detector.Operator(greyscaleMatrix, out IEnumerable<RectDetection>? 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<byte>? 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<RectDetection>? 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<RgbPixel>? rgbMatrix = img.Matrix as Matrix<RgbPixel>;
if (upsamplingAmount == 0)
{
detector.Operator(rgbMatrix, out IEnumerable<RectDetection>? 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<RgbPixel>? 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<RectDetection>? 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<Tuple<Rectangle, double>> 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<double>? detectionConfidences = [];
List<ulong>? 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<Rectangle, double>(rect, detectionConfidences[index++]);
}
#region Helpers
private static void SplitRectDetections(RectDetection[] rectDetections,
List<Rectangle> rectangles,
List<double> detectionConfidences,
List<ulong> 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
}

View File

@ -0,0 +1,27 @@
using DlibDotNet;
using View_by_Distance.FaceRecognitionDotNet.Models;
using View_by_Distance.Shared.Models;
namespace View_by_Distance.FaceRecognitionDotNet.Extensions;
/// <summary>
/// An abstract base class that provides functionality to detect face locations from image.
/// </summary>
public abstract class FaceDetector : DisposableObject
{
#region Methods
internal IEnumerable<Location> Detect(Image image, int numberOfTimesToUpsample) => RawDetect(image.Matrix, numberOfTimesToUpsample);
/// <summary>
/// Returns an enumerable collection of face location correspond to all faces in specified image.
/// </summary>
/// <param name="matrix">The matrix contains a face.</param>
/// <param name="numberOfTimesToUpsample">The number of times to up-sample the image when finding faces.</param>
/// <returns>An enumerable collection of face location correspond to all faces.</returns>
protected abstract IEnumerable<Location> RawDetect(MatrixBase matrix, int numberOfTimesToUpsample);
#endregion
}

View File

@ -0,0 +1,37 @@
using DlibDotNet;
using View_by_Distance.FaceRecognitionDotNet.Models;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Stateless;
namespace View_by_Distance.FaceRecognitionDotNet.Extensions;
/// <summary>
/// An abstract base class that provides functionality to detect face parts locations from face image.
/// </summary>
public abstract class FaceLandmarkDetector : DisposableObject
{
#region Methods
internal FullObjectDetection Detect(Image image, Location location) => RawDetect(image.Matrix, location);
internal IEnumerable<Dictionary<FacePart, IEnumerable<FacePoint>>> GetLandmarks(IEnumerable<FacePoint[]> landmarkTuples) => RawGetLandmarks(landmarkTuples);
/// <summary>
/// Returns an object contains information of face parts corresponds to specified location in specified image.
/// </summary>
/// <param name="matrix">The matrix contains a face.</param>
/// <param name="location">The location rectangle for a face.</param>
/// <returns>An object contains information of face parts.</returns>
protected abstract FullObjectDetection RawDetect(MatrixBase matrix, Location location);
/// <summary>
/// Returns an enumerable collection of dictionary of face parts locations (eyes, nose, etc).
/// </summary>
/// <param name="landmarkTuples">The enumerable collection of face parts location.</param>
/// <returns>An enumerable collection of dictionary of face parts locations (eyes, nose, etc).</returns>
protected abstract IEnumerable<Dictionary<FacePart, IEnumerable<FacePoint>>> RawGetLandmarks(IEnumerable<FacePoint[]> landmarkTuples);
#endregion
}

View File

@ -0,0 +1,97 @@
namespace View_by_Distance.FaceRecognitionDotNet.Models;
/// <summary>
/// Represents a class which has managed or unmanaged resources.
/// </summary>
public abstract class DisposableObject : IDisposable
{
#region Properties
/// <summary>
/// Gets a value indicating whether this instance has been disposed.
/// </summary>
/// <returns>true if this instance has been disposed; otherwise, false.</returns>
public bool IsDisposed
{
get;
private set;
}
#endregion
#region Methods
/// <summary>
/// If this object is disposed, then <see cref="ObjectDisposedException"/> is thrown.
/// </summary>
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
/// <summary>
/// Releases all managed resources.
/// </summary>
protected virtual void DisposeManaged()
{
}
/// <summary>
/// Releases all unmanaged resources.
/// </summary>
protected virtual void DisposeUnmanaged()
{
}
#endregion
#endregion
#region IDisposable Members
/// <summary>
/// Releases all resources used by this <see cref="DisposableObject"/>.
/// </summary>
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
/// <summary>
/// Releases all resources used by this <see cref="DisposableObject"/>.
/// </summary>
/// <param name="disposing">Indicate value whether <see cref="IDisposable.Dispose"/> method was called.</param>
private void Dispose(bool disposing)
{
if (IsDisposed)
{
return;
}
IsDisposed = true;
if (disposing)
DisposeManaged();
DisposeUnmanaged();
}
#endregion
}

View File

@ -0,0 +1,105 @@
using DlibDotNet;
using System.Runtime.Serialization;
namespace View_by_Distance.FaceRecognitionDotNet.Models;
/// <summary>
/// Represents a feature data of face. This class cannot be inherited.
/// </summary>
[Serializable]
public sealed class FaceEncoding : DisposableObject, ISerializable
{
#region Fields
[NonSerialized]
private readonly Matrix<double> _Encoding;
#endregion
#region Constructors
internal FaceEncoding(Matrix<double> 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<double>(array, row.Value, column.Value);
}
#endregion
#region Properties
internal Matrix<double> Encoding => _Encoding;
/// <summary>
/// Gets the size of feature data.
/// </summary>
/// <exception cref="ObjectDisposedException">This object is disposed.</exception>
public int Size
{
get
{
ThrowIfDisposed();
return _Encoding.Size;
}
}
#endregion
#region Methods
/// <summary>
/// Gets a feature data of face as raw format.
/// </summary>
/// <returns>A <see cref="double"/> array that represents a feature data.</returns>
/// <remarks><see cref="FaceEncoding"/> class supports serialization. This method is for interoperability between FaceRecognitionotNet and dlib.</remarks>
/// <exception cref="ObjectDisposedException">This object is disposed.</exception>
public double[] GetRawEncoding()
{
ThrowIfDisposed();
return _Encoding.ToArray();
}
#region Overrides
/// <summary>
/// Releases all unmanaged resources.
/// </summary>
protected override void DisposeUnmanaged()
{
base.DisposeUnmanaged();
_Encoding?.Dispose();
}
#endregion
#endregion
#region ISerializable Members
/// <summary>
/// Populates a <see cref="SerializationInfo"/> with the data needed to serialize the target object.
/// </summary>
/// <param name="info">The <see cref="SerializationInfo"/> to populate with data.</param>
/// <param name="context">The destination (see <see cref="StreamingContext"/>) for this serialization.</param>
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
}

View File

@ -0,0 +1,496 @@
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;
namespace View_by_Distance.FaceRecognitionDotNet.Models;
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<FaceEncoding?> FaceEncodings, List<List<FacePartAndFacePointArray>> 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<double>? diff = faceEncoding.Encoding - faceToCompare.Encoding;
return DlibDotNet.Dlib.Length(diff);
}
private static FacePoint[] Join(IEnumerable<FacePoint> facePoints1, IEnumerable<FacePoint> facePoints2)
{
List<FacePoint> results = [.. facePoints1, .. facePoints2];
return results.ToArray();
}
private List<FacePartAndFacePointArray> GetFaceParts(FullObjectDetection fullObjectDetection)
{
List<FacePartAndFacePointArray> 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<Tuple<DlibDotNet.Rectangle, double>>? 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<Location> FaceLocations(Image image)
{
if (image is null)
throw new NullReferenceException(nameof(image));
image.ThrowIfDisposed();
ThrowIfDisposed();
List<Location> results = [];
System.Drawing.Rectangle rectangle;
IEnumerable<MModRect> 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<FullObjectDetection> GetFullObjectDetections(Image image, List<Location> locations)
{
List<FullObjectDetection> 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<Location> GetLocations(Image image)
{
List<Location> 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<FaceRecognitionGroup> GetCollection(Image image, List<Location> locations, bool includeFaceEncoding, bool includeFaceParts)
{
List<FaceRecognitionGroup> 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<FullObjectDetection> fullObjectDetections = GetFullObjectDetections(image, locations);
if (fullObjectDetections.Count != locations.Count)
throw new Exception();
Record record;
List<Record> 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<double> 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<FacePartAndFacePointArray> 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<FacePart, FacePoint[]> 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<double>? matrix = Matrix<double>.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<double>? matrix = Matrix<double>.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<RgbPixel>(file), mode),
Mode.Greyscale => new Image(DlibDotNet.Dlib.LoadImageAsMatrix<byte>(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<RgbPixel>(ptr, height, width, width * 3), Mode.Rgb);
case Mode.Greyscale:
return new Image(new Matrix<byte>(ptr, height, width, width), Mode.Greyscale);
default:
break;
}
}
}
}
finally
{
if (data != null)
bitmap.UnlockBits(data);
}
return null;
}
public static ReadOnlyCollection<LocationContainer> GetLocationContainers(int permyriad, ReadOnlyCollection<LocationContainer> locationContainers, LocationContainer locationContainer)
{
List<LocationContainer> 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<double> 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<FaceDistance> FaceDistances(ReadOnlyCollection<FaceDistance> faceDistances, FaceDistance faceDistanceToCompare)
{
List<FaceDistance> 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<double> 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();
}
}

View File

@ -0,0 +1,6 @@
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Stateless;
namespace View_by_Distance.FaceRecognitionDotNet.Models;
public record FaceRecognitionGroup(Location Location, FaceEncoding? FaceEncoding, Dictionary<FacePart, FacePoint[]>? KeyValuePairs);

View File

@ -0,0 +1,22 @@
namespace View_by_Distance.FaceRecognitionDotNet.Models;
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";
}

View File

@ -0,0 +1,129 @@
using DlibDotNet;
using DlibDotNet.Extensions;
using System.Drawing;
using View_by_Distance.Shared.Models.Stateless;
namespace View_by_Distance.FaceRecognitionDotNet.Models;
/// <summary>
/// Represents a image data. This class cannot be inherited.
/// </summary>
public sealed class Image : DisposableObject
{
#region Fields
#endregion
#region Constructors
internal Image(MatrixBase matrix, Mode mode)
{
Matrix = matrix;
Mode = mode;
}
#endregion
#region Properties
/// <summary>
/// Gets the height of the image.
/// </summary>
/// <exception cref="ObjectDisposedException">This object is disposed.</exception>
public int Height
{
get
{
ThrowIfDisposed();
return Matrix.Rows;
}
}
internal MatrixBase Matrix { get; private set; }
internal Mode Mode { get; }
/// <summary>
/// Gets the width of the image.
/// </summary>
/// <exception cref="ObjectDisposedException">This object is disposed.</exception>
public int Width
{
get
{
ThrowIfDisposed();
return Matrix.Columns;
}
}
#endregion
#region Methods
/// <summary>
/// Saves this <see cref="Image"/> to the specified file.
/// </summary>
/// <param name="fileName">A string that contains the name of the file to which to save this <see cref="Image"/>.</param>
/// <param name="format">The <see cref="ImageFormat"/> for this <see cref="Image"/>.</param>
/// <exception cref="NullReferenceException"><paramref name="fileName"/> is null.</exception>
/// <exception cref="ObjectDisposedException">This object is disposed.</exception>
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;
}
}
/// <summary>
/// Converts this <see cref="Image"/> to a GDI+ <see cref="Bitmap"/>.
/// </summary>
/// <returns>A <see cref="Bitmap"/> that represents the converted <see cref="Image"/>.</returns>
/// <exception cref="ObjectDisposedException">This object is disposed.</exception>
/// <exception cref="NotSupportedException">A Greyscale image is not supported.</exception>
public Bitmap ToBitmap()
{
ThrowIfDisposed();
if (Mode == Mode.Greyscale)
throw new NotSupportedException();
return ((Matrix<RgbPixel>)Matrix).ToBitmap();
}
#region Overrides
/// <summary>
/// Releases all unmanaged resources.
/// </summary>
protected override void DisposeUnmanaged()
{
base.DisposeUnmanaged();
Matrix?.Dispose();
}
#endregion
#endregion
}

View File

@ -0,0 +1,49 @@
namespace View_by_Distance.FaceRecognitionDotNet.Models;
/// <summary>
/// Describes the model binary datum. This class cannot be inherited.
/// </summary>
public sealed class ModelParameter
{
#region Properties
/// <summary>
/// Gets or sets the binary data of model for 68 points face landmarks.
/// </summary>
public byte[]? PosePredictor68FaceLandmarksModel
{
get;
set;
}
/// <summary>
/// Gets or sets the binary data of model for 5 points face landmarks.
/// </summary>
public byte[]? PosePredictor5FaceLandmarksModel
{
get;
set;
}
/// <summary>
/// Gets or sets the binary data of model for face encoding.
/// </summary>
public byte[]? FaceRecognitionModel
{
get;
set;
}
/// <summary>
/// Gets or sets the binary data of model for face detector by using CNN.
/// </summary>
public byte[]? CnnFaceDetectorModel
{
get;
set;
}
#endregion
}

View File

@ -0,0 +1,107 @@
namespace View_by_Distance.FaceRecognitionDotNet.Models;
/// <summary>
/// Represents an ordered pair of integer x- and y-coordinates that defines a point in a two-dimensional plane.
/// </summary>
public readonly struct Point : IEquatable<Point>
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Point"/> structure with the specified coordinates.
/// </summary>
/// <param name="x">The horizontal position of the point.</param>
/// <param name="y">The vertical position of the point.</param>
public Point(int x, int y)
{
X = x;
Y = y;
}
internal Point(DlibDotNet.Point point)
{
X = point.X;
Y = point.Y;
}
#endregion
#region Properties
/// <summary>
/// Gets the x-coordinate of this <see cref="Point"/>.
/// </summary>
public int X
{
get;
}
/// <summary>
/// Gets the y-coordinate of this <see cref="Point"/>.
/// </summary>
public int Y
{
get;
}
#endregion
#region Methods
/// <summary>
/// Compares two <see cref="Point"/> structures for equality.
/// </summary>
/// <param name="other">The point to compare to this instance.</param>
/// <returns><code>true</code> if both <see cref="Point"/> structures contain the same <see cref="X"/> and <see cref="Y"/> values; otherwise, <code>false</code>.</returns>
public bool Equals(Point other)
{
return X == other.X &&
Y == other.Y;
}
#region overrides
/// <summary>
/// Determines whether the specified <see cref="object"/> is a <see cref="Point"/> and whether it contains the same coordinates as this <see cref="Point"/>.
/// </summary>
/// <param name="obj">The <see cref="object"/> to compare.</param>
/// <returns><code>true</code> if <paramref name="obj"/> is a <see cref="Point"/> and contains the same <see cref="X"/> and <see cref="Y"/> values as this <see cref="Point"/>; otherwise, <code>false</code>.</returns>
public override bool Equals(object? obj) => obj is Point point && Equals(point);
/// <summary>
/// Returns the hash code for this <see cref="Point"/>.
/// </summary>
/// <returns>The hash code for this <see cref="Point"/> structure.</returns>
#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;
}
/// <summary>
/// Compares two <see cref="Point"/> structures for equality.
/// </summary>
/// <param name="point1">The first <see cref="Point"/> structure to compare.</param>
/// <param name="point2">The second <see cref="Point"/> structure to compare.</param>
/// <returns><code>true</code> if both the <see cref="X"/> and <see cref="Y"/> coordinates of <paramref name="point1"/> and <paramref name="point2"/> are equal; otherwise, <code>false</code>.</returns>
public static bool operator ==(Point point1, Point point2) => point1.Equals(point2);
/// <summary>
/// Compares two <see cref="Point"/> structures for inequality.
/// </summary>
/// <param name="point1">The first <see cref="Point"/> structure to compare.</param>
/// <param name="point2">The second <see cref="Point"/> structure to compare.</param>
/// <returns><code>true</code> if <paramref name="point1"/> and <paramref name="point2"/> have different <see cref="X"/> or <see cref="Y"/> coordinates; <code>false</code> if <paramref name="point1"/> and <paramref name="point2"/> have the same <see cref="X"/> and <see cref="Y"/> coordinates.</returns>
public static bool operator !=(Point point1, Point point2) => !(point1 == point2);
#endregion
#endregion
}

1
Metadata/.vscode/read-me.md vendored Normal file
View File

@ -0,0 +1 @@
# Read Me

View File

@ -1,43 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>library</OutputType>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<PackageId>Phares.AA.Metadata</PackageId>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<Version>8.0.101.1</Version>
<Authors>Mike Phares</Authors>
<Company>Phares</Company>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>Windows</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)'=='true'">
<DefineConstants>OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>Linux</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MetadataExtractor" Version="2.8.1" />
<PackageReference Include="System.Text.Json" Version="7.0.3" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\AA.Shared.csproj" />
</ItemGroup>
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>library</OutputType>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<PackageId>Phares.AA.Metadata</PackageId>
<Version>8.0.112.0</Version>
<Company>Phares</Company>
<Authors>Mike Phares</Authors>
<IncludeSymbols>true</IncludeSymbols>
<PackageReadmeFile>read-me.md</PackageReadmeFile>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>Windows</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)'=='true'">
<DefineConstants>OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>Linux</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<None Include=".vscode\read-me.md" Pack="true" PackagePath="\"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="MetadataExtractor" Version="2.8.1" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\AA.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -1,60 +1,89 @@
using MetadataExtractor;
using System.Collections.ObjectModel;
using System.Text.Json;
using View_by_Distance.Metadata.Models.Stateless;
using View_by_Distance.Metadata.Models.Stateless.Methods;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Stateless.Methods;
using View_by_Distance.Shared.Models.Stateless;
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<int, ReadOnlyDictionary<string, string[]>> _FileGroups;
public A_Metadata(MetadataConfiguration metadataConfiguration)
public A_Metadata(ResultSettings resultSettings, MetadataSettings metadataSettings)
{
_MetadataConfiguration = metadataConfiguration;
string bResultsFullGroupDirectory = 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, bResultsFullGroupDirectory, [metadataConfiguration.ResultConfiguration.ResultSingleton]);
_FileGroups = IPath.GetKeyValuePairs(resultSettings, aResultsFullGroupDirectory, [resultSettings.ResultSingleton]);
}
private FileInfo GetFileInfo(ResultConfiguration resultConfiguration, FilePath filePath)
private MinimumYearAndPathCombined GetMinimumYearAndPathCombined(ResultSettings resultSettings, FilePath filePath)
{
FileInfo result;
FileInfo fileInfo = new(filePath.FullName);
(_, int directoryIndex) = IPath.GetDirectoryNameAndIndex(resultConfiguration, filePath);
DateTime minimumDateTime = fileInfo.CreationTime < fileInfo.LastWriteTime ? fileInfo.CreationTime : fileInfo.LastWriteTime;
int minimumYear = minimumDateTime.Year < resultConfiguration.EpicYear ? resultConfiguration.EpicYear : minimumDateTime.Year;
result = new(Path.Combine(_FileGroups[minimumYear][_MetadataConfiguration.ResultConfiguration.ResultSingleton][directoryIndex], $"{filePath.NameWithoutExtension}{filePath.ExtensionLowered}.json"));
MinimumYearAndPathCombined result;
(_, int directoryIndex) = IPath.GetDirectoryNameAndIndex(resultSettings, filePath);
DateTime minimumDateTime = new(filePath.CreationTicks < filePath.LastWriteTicks ? filePath.CreationTicks : filePath.LastWriteTicks);
int minimumYear = minimumDateTime.Year < resultSettings.EpicYear ? resultSettings.EpicYear : minimumDateTime.Year;
result = new(minimumYear, Path.Combine(_FileGroups[minimumYear][_ResultSettings.ResultSingleton][directoryIndex], $"{filePath.NameWithoutExtension}{filePath.ExtensionLowered}.json"));
return result;
}
public (FileInfo, ExifDirectory) GetMetadataCollection(MetadataConfiguration metadataConfiguration, FilePath filePath, DeterministicHashCode deterministicHashCode)
private (int, string) GetJsonFile(ResultSettings resultSettings, FilePath filePath, ExifDirectory exifDirectory)
{
ExifDirectory? results;
FileInfo fileInfo = GetFileInfo(metadataConfiguration.ResultConfiguration, filePath);
if (_MetadataConfiguration.ForceMetadataLastWriteTimeToCreationTime && !fileInfo.Exists && File.Exists(Path.ChangeExtension(fileInfo.FullName, ".delete")))
string? result;
DateTime? dateTime;
dateTime = IDate.GetDateTimeOriginal(exifDirectory);
dateTime ??= IDate.GetMinimum(exifDirectory);
(_, 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 new(exifYear, result);
}
private static (string, ExifDirectory?) Get(string jsonFile)
{
ExifDirectory? result;
string json = File.ReadAllText(jsonFile);
try
{
result = JsonSerializer.Deserialize(json, ExifDirectorySourceGenerationContext.Default.ExifDirectory);
if (result is null)
throw new Exception();
}
catch (Exception)
{
result = null;
}
return new(json, result);
}
public (MinimumYearAndPathCombined, ExifDirectory) GetMetadataCollection(ResultSettings resultSettings, MetadataSettings metadataSettings, FilePath filePath)
{
ExifDirectory? result;
MinimumYearAndPathCombined minimumYearAndPathCombined = GetMinimumYearAndPathCombined(resultSettings, filePath);
FileInfo fileInfo = new(minimumYearAndPathCombined.PathCombined);
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)
results = null;
if (_MetadataSettings.PropertiesChangedForMetadata)
result = null;
else if (!fileInfo.Exists)
results = null;
result = null;
else if (!fileInfo.FullName.EndsWith(".json") && !fileInfo.FullName.EndsWith(".old"))
throw new ArgumentException("must be a *.json file");
else
@ -62,60 +91,79 @@ public class A_Metadata
string json = File.ReadAllText(fileInfo.FullName);
try
{
results = JsonSerializer.Deserialize(json, ExifDirectorySourceGenerationContext.Default.ExifDirectory);
if (results is null)
result = JsonSerializer.Deserialize(json, ExifDirectorySourceGenerationContext.Default.ExifDirectory);
if (result is null)
throw new Exception();
}
catch (Exception)
{
results = null;
result = null;
}
}
if (results is null)
if (result is null)
{
System.Drawing.Size? size;
try
{ size = Dimensions.GetDimensions(filePath.FullName); }
catch (Exception) { size = null; }
IReadOnlyList<MetadataExtractor.Directory> directories = ImageMetadataReader.ReadMetadata(filePath.FullName);
results = Exif.Covert(filePath, deterministicHashCode, size, directories);
string json = JsonSerializer.Serialize(results, ExifDirectorySourceGenerationContext.Default.ExifDirectory);
if (IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null) && _MetadataConfiguration.ForceMetadataLastWriteTimeToCreationTime)
string json;
Stream stream = File.OpenRead(filePath.FullName);
const PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName = null;
result = Exif.GetExifDirectory(filePath, stream, personKeyFormattedAndKeyTicksAndDisplayDirectoryName);
stream.Dispose();
(int exifYear, string jsonFile) = GetJsonFile(_ResultSettings, filePath, result);
if (exifYear == minimumYearAndPathCombined.MinimumYear)
json = JsonSerializer.Serialize(result, ExifDirectorySourceGenerationContext.Default.ExifDirectory);
else
{
fileInfo = new(jsonFile);
if (!File.Exists(jsonFile))
json = JsonSerializer.Serialize(result, ExifDirectorySourceGenerationContext.Default.ExifDirectory);
else
{
(string checkJson, ExifDirectory? exifDirectory) = Get(jsonFile);
if (exifDirectory is null)
json = JsonSerializer.Serialize(result, ExifDirectorySourceGenerationContext.Default.ExifDirectory);
else
{
json = checkJson;
result = exifDirectory;
}
}
}
if (IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null) && _MetadataSettings.ForceMetadataLastWriteTimeToCreationTime)
{
File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime);
fileInfo.Refresh();
}
}
return (fileInfo, results);
return new(minimumYearAndPathCombined, result);
}
public static Action<string> SetExifDirectoryCollection(IRename rename, MetadataConfiguration metadataConfiguration, A_Metadata metadata, List<(string, FileInfo, ExifDirectory)> exifDirectories, Action tick)
private static Stream GetStream(HttpClient httpClient, FilePath filePath)
{
return file =>
Stream result;
Task<HttpResponseMessage> httpResponseMessage = httpClient.GetAsync(filePath.FullName);
httpResponseMessage.Wait();
Task task = httpResponseMessage.Result.Content.LoadIntoBufferAsync();
task.Wait();
Task<Stream> stream = httpResponseMessage.Result.Content.ReadAsStreamAsync();
stream.Wait();
result = stream.Result;
return result;
}
public (MinimumYearAndPathCombined, ExifDirectory) GetMetadataCollection(ResultSettings resultSettings, MetadataSettings metadataSettings, HttpClient? httpClient, FilePath filePath)
{
ExifDirectory result;
MinimumYearAndPathCombined minimumYearAndPathCombined;
if (httpClient is null)
(minimumYearAndPathCombined, result) = GetMetadataCollection(resultSettings, metadataSettings, filePath);
else
{
tick.Invoke();
FileInfo fileInfo;
FilePath? ffmpegFilePath;
ExifDirectory exifDirectory;
ReadOnlyCollection<string> ffmpegFiles;
DeterministicHashCode deterministicHashCode;
FilePath filePath = IId.GetFilePath(metadataConfiguration, file);
if (filePath.ExtensionLowered is not ".paddedId" and not ".lsv")
{
if (filePath.Id is null || (!filePath.IsIdFormat && !filePath.IsPaddedIdFormat))
{
(ffmpegFiles, ffmpegFilePath) = rename.ConvertAndGetFfmpegFiles(filePath);
if (ffmpegFilePath is not null)
filePath = ffmpegFilePath;
deterministicHashCode = filePath.Id is not null ? deterministicHashCode = new(null, filePath.Id, null) : deterministicHashCode = rename.GetDeterministicHashCode(filePath);
(fileInfo, exifDirectory) = metadata.GetMetadataCollection(metadataConfiguration, filePath, deterministicHashCode);
lock (exifDirectories)
exifDirectories.Add(new(file, fileInfo, exifDirectory));
foreach (string ffmpegFile in ffmpegFiles)
File.Delete(ffmpegFile);
}
}
};
Stream stream = GetStream(httpClient, filePath);
minimumYearAndPathCombined = GetMinimumYearAndPathCombined(resultSettings, filePath);
const PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName = null;
result = Exif.GetExifDirectory(filePath, stream, personKeyFormattedAndKeyTicksAndDisplayDirectoryName);
stream.Dispose();
}
return new(minimumYearAndPathCombined, result);
}
}

View File

@ -1,2 +0,0 @@
[*.cs]
csharp_preserve_single_line_statements = true

View File

@ -1,78 +0,0 @@
using Microsoft.Extensions.Configuration;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace View_by_Distance.Metadata.Models.Binder;
public class MetadataConfiguration
{
public bool? ForceMetadataLastWriteTimeToCreationTime { get; set; }
public string[]? IgnoreRulesKeyWords { get; set; }
public int? Offset { get; set; }
public bool? PropertiesChangedForMetadata { get; set; }
public override string ToString()
{
string result = JsonSerializer.Serialize(this, BinderMetadataConfigurationSourceGenerationContext.Default.MetadataConfiguration);
return result;
}
private static void PreVerify(IConfigurationRoot configurationRoot, MetadataConfiguration? configuration)
{
if (configuration?.ForceMetadataLastWriteTimeToCreationTime is null)
{
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;
if (!physicalFileProvider.Root.Contains("UserSecrets"))
continue;
throw new NotSupportedException(physicalFileProvider.Root);
}
}
}
private static void Verify(MetadataConfiguration configuration)
{
if (configuration.IgnoreRulesKeyWords is null || configuration.IgnoreRulesKeyWords.Length == 0) throw new NullReferenceException(nameof(configuration.IgnoreRulesKeyWords));
}
private static Shared.Models.MetadataConfiguration Get(MetadataConfiguration? configuration, Shared.Models.ResultConfiguration resultConfiguration)
{
Shared.Models.MetadataConfiguration result;
if (configuration is null) throw new NullReferenceException(nameof(configuration));
if (configuration.ForceMetadataLastWriteTimeToCreationTime is null) throw new NullReferenceException(nameof(configuration.ForceMetadataLastWriteTimeToCreationTime));
if (configuration.IgnoreRulesKeyWords is null) throw new NullReferenceException(nameof(configuration.IgnoreRulesKeyWords));
if (configuration.Offset is null) throw new NullReferenceException(nameof(configuration.Offset));
if (configuration.PropertiesChangedForMetadata is null) throw new NullReferenceException(nameof(configuration.PropertiesChangedForMetadata));
Verify(configuration);
result = new(resultConfiguration,
configuration.ForceMetadataLastWriteTimeToCreationTime.Value,
configuration.IgnoreRulesKeyWords,
configuration.Offset.Value,
configuration.PropertiesChangedForMetadata.Value);
return result;
}
public static Shared.Models.MetadataConfiguration Get(IConfigurationRoot configurationRoot, Shared.Models.ResultConfiguration resultConfiguration)
{
Shared.Models.MetadataConfiguration result;
IConfigurationSection configurationSection = configurationRoot.GetSection(nameof(Shared.Models.MetadataConfiguration));
#pragma warning disable IL3050, IL2026
MetadataConfiguration? configuration = configurationSection.Get<MetadataConfiguration>();
#pragma warning restore IL3050, IL2026
PreVerify(configurationRoot, configuration);
result = Get(configuration, resultConfiguration);
return result;
}
}
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(MetadataConfiguration))]
internal partial class BinderMetadataConfigurationSourceGenerationContext : JsonSerializerContext
{
}

View File

@ -1,99 +0,0 @@
using Microsoft.Extensions.Configuration;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace View_by_Distance.Metadata.Models.Binder;
public class ResultConfiguration
{
public string? DateGroup { get; set; }
public int? EpicYear { get; set; }
public string? ModelName { get; set; }
public int? NumberOfJitters { get; set; }
public int? NumberOfTimesToUpsample { get; set; }
public string? PredictorModelName { get; set; }
public int? ResultAllInOneSubdirectoryLength { get; set; }
public string? ResultCollection { get; set; }
public string? ResultContent { get; set; }
public string? ResultSingleton { get; set; }
public string? RootDirectory { get; set; }
public override string ToString()
{
string result = JsonSerializer.Serialize(this, BinderResultConfigurationSourceGenerationContext.Default.ResultConfiguration);
return result;
}
private static void PreVerify(IConfigurationRoot configurationRoot, ResultConfiguration? configuration)
{
if (configuration?.DateGroup is null)
{
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;
if (!physicalFileProvider.Root.Contains("UserSecrets"))
continue;
throw new NotSupportedException(physicalFileProvider.Root);
}
}
}
public static void Verify(ResultConfiguration configuration, bool requireRootDirectoryExists)
{
if (string.IsNullOrEmpty(configuration.DateGroup)) throw new NullReferenceException(nameof(configuration.DateGroup));
if (string.IsNullOrEmpty(configuration.RootDirectory) || (requireRootDirectoryExists && !Directory.Exists(configuration.RootDirectory))) throw new NullReferenceException(nameof(configuration.RootDirectory));
}
private static Shared.Models.ResultConfiguration Get(ResultConfiguration? configuration, bool requireRootDirectoryExists)
{
Shared.Models.ResultConfiguration result;
if (configuration is null) throw new NullReferenceException(nameof(configuration));
if (configuration.DateGroup is null) throw new NullReferenceException(nameof(configuration.DateGroup));
if (configuration.EpicYear is null) throw new NullReferenceException(nameof(configuration.EpicYear));
// if (configuration.ModelName is null) throw new NullReferenceException(nameof(configuration.ModelName));
// if (configuration.NumberOfJitters is null) throw new NullReferenceException(nameof(configuration.NumberOfJitters));
// if (configuration.NumberOfTimesToUpsample is null) throw new NullReferenceException(nameof(configuration.NumberOfTimesToUpsample));
// if (configuration.PredictorModelName is null) throw new NullReferenceException(nameof(configuration.PredictorModelName));
if (configuration.ResultAllInOneSubdirectoryLength is null) throw new NullReferenceException(nameof(configuration.ResultAllInOneSubdirectoryLength));
if (configuration.ResultCollection is null) throw new NullReferenceException(nameof(configuration.ResultCollection));
if (configuration.ResultContent is null) throw new NullReferenceException(nameof(configuration.ResultContent));
if (configuration.ResultSingleton is null) throw new NullReferenceException(nameof(configuration.ResultSingleton));
if (configuration.RootDirectory is null) throw new NullReferenceException(nameof(configuration.RootDirectory));
Verify(configuration, requireRootDirectoryExists);
result = new(configuration.DateGroup,
configuration.EpicYear.Value,
configuration.ModelName,
configuration.NumberOfJitters,
configuration.NumberOfTimesToUpsample,
configuration.PredictorModelName,
configuration.ResultAllInOneSubdirectoryLength.Value,
configuration.ResultCollection,
configuration.ResultContent,
configuration.ResultSingleton,
Path.GetFullPath(configuration.RootDirectory));
return result;
}
public static Shared.Models.ResultConfiguration Get(IConfigurationRoot configurationRoot, bool requireRootDirectoryExists)
{
Shared.Models.ResultConfiguration result;
IConfigurationSection configurationSection = configurationRoot.GetSection(nameof(Shared.Models.ResultConfiguration));
#pragma warning disable IL3050, IL2026
ResultConfiguration? configuration = configurationSection.Get<ResultConfiguration>();
#pragma warning restore IL3050, IL2026
PreVerify(configurationRoot, configuration);
result = Get(configuration, requireRootDirectoryExists);
return result;
}
}
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(ResultConfiguration))]
internal partial class BinderResultConfigurationSourceGenerationContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,68 @@
using System.Collections.ObjectModel;
using View_by_Distance.Shared.Models;
namespace View_by_Distance.Metadata.Models.Stateless;
internal static class Base
{
internal static string? GetMaker(ExifDirectoryBase[]? exifBaseDirectories)
{
string? result = null;
if (exifBaseDirectories is not null)
{
string value;
foreach (ExifDirectoryBase exifDirectoryBase in exifBaseDirectories)
{
value = exifDirectoryBase?.Make is null ? string.Empty : exifDirectoryBase.Make.ToString().Trim();
if (string.IsNullOrEmpty(value))
result = null;
else
{
result = $"{value[0].ToString().ToUpper()}{value[1..].ToLower()}";
break;
}
}
}
return result;
}
internal static string? GetModel(ExifDirectoryBase[]? exifBaseDirectories)
{
string? result = null;
if (exifBaseDirectories is not null)
{
string value;
foreach (ExifDirectoryBase exifDirectoryBase in exifBaseDirectories)
{
value = exifDirectoryBase?.Model is null ? string.Empty : exifDirectoryBase.Model.ToString().Trim();
if (string.IsNullOrEmpty(value))
result = null;
else
{
result = value;
break;
}
}
}
return result;
}
internal static ReadOnlyCollection<string> GetKeywords(ExifDirectoryBase[]? exifBaseDirectories)
{
List<string> results = [];
if (exifBaseDirectories is not null)
{
string value;
foreach (ExifDirectoryBase exifDirectoryBase in exifBaseDirectories)
{
value = exifDirectoryBase?.WinKeywords is null ? string.Empty : exifDirectoryBase.WinKeywords.ToString().Trim();
if (string.IsNullOrEmpty(value))
continue;
results.Add(value);
}
}
return results.AsReadOnly();
}
}

View File

@ -1,14 +1,12 @@
using System.Drawing;
namespace View_by_Distance.Metadata.Models.Stateless.Methods;
namespace View_by_Distance.Metadata.Models.Stateless;
internal static class Dimensions
{
const string _ErrorMessage = "Could not recognize image format.";
#pragma warning disable IDE0230
private static readonly Dictionary<byte[], Func<BinaryReader, Size>> _ImageFormatDecoders = new()
private static readonly Dictionary<byte[], Func<BinaryReader, Size?>> _ImageFormatDecoders = new()
{
{ new byte[] { 0x42, 0x4D }, DecodeBitmap },
{ new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif },
@ -23,10 +21,9 @@ internal static class Dimensions
{
for (int i = 0; i < thatBytes.Length; i += 1)
{
if (thisBytes[i] != thatBytes[i])
{
return false;
}
if (thisBytes[i] == thatBytes[i])
continue;
return false;
}
return true;
}
@ -35,9 +32,7 @@ internal static class Dimensions
{
byte[] bytes = new byte[sizeof(short)];
for (int i = 0; i < sizeof(short); i += 1)
{
bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte();
}
return BitConverter.ToInt16(bytes, 0);
}
@ -45,13 +40,11 @@ internal static class Dimensions
{
byte[] bytes = new byte[sizeof(int)];
for (int i = 0; i < sizeof(int); i += 1)
{
bytes[sizeof(int) - 1 - i] = binaryReader.ReadByte();
}
return BitConverter.ToInt32(bytes, 0);
}
private static Size DecodeBitmap(BinaryReader binaryReader)
private static Size? DecodeBitmap(BinaryReader binaryReader)
{
_ = binaryReader.ReadBytes(16);
int width = binaryReader.ReadInt32();
@ -59,14 +52,14 @@ internal static class Dimensions
return new Size(width, height);
}
private static Size DecodeGif(BinaryReader binaryReader)
private static Size? DecodeGif(BinaryReader binaryReader)
{
int width = binaryReader.ReadInt16();
int height = binaryReader.ReadInt16();
return new Size(width, height);
}
private static Size DecodePng(BinaryReader binaryReader)
private static Size? DecodePng(BinaryReader binaryReader)
{
_ = binaryReader.ReadBytes(8);
int width = ReadLittleEndianInt32(binaryReader);
@ -74,100 +67,66 @@ internal static class Dimensions
return new Size(width, height);
}
private static Size DecodeJfif(BinaryReader binaryReader)
private static Size? DecodeJfif(BinaryReader binaryReader)
{
while (binaryReader.ReadByte() == 0xff)
{
byte marker = binaryReader.ReadByte();
short chunkLength = ReadLittleEndianInt16(binaryReader);
if (marker == 0xc0)
{
_ = binaryReader.ReadByte();
int height = ReadLittleEndianInt16(binaryReader);
int width = ReadLittleEndianInt16(binaryReader);
return new Size(width, height);
}
if (chunkLength < 0)
if (chunkLength >= 0)
_ = binaryReader.ReadBytes(chunkLength - 2);
else
{
ushort uChunkLength = (ushort)chunkLength;
_ = binaryReader.ReadBytes(uChunkLength - 2);
}
else
{
_ = binaryReader.ReadBytes(chunkLength - 2);
}
}
throw new ArgumentException(_ErrorMessage);
return null;
}
private static Size DecodeWebP(BinaryReader binaryReader)
private static Size? DecodeWebP(BinaryReader binaryReader)
{
_ = binaryReader.ReadUInt32(); // Size
_ = binaryReader.ReadBytes(15); // WEBP, VP8 + more
_ = binaryReader.ReadBytes(3); // SYNC
int width = binaryReader.ReadUInt16() & 0b00_11111111111111; // 14 bits width
int height = binaryReader.ReadUInt16() & 0b00_11111111111111; // 14 bits height
return new Size(width, height);
}
/// <summary>
/// Gets the dimensions of an image.
/// </summary>
/// <param name="path">The path of the image to get the dimensions of.</param>
/// <returns>The dimensions of the specified image.</returns>
/// <exception cref="ArgumentException">The image was of an unrecognized format.</exception>
internal static Size GetDimensions(BinaryReader binaryReader)
internal static Size? GetDimensions(BinaryReader binaryReader)
{
int maxMagicBytesLength = _ImageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length;
byte[] magicBytes = new byte[maxMagicBytesLength];
for (int i = 0; i < maxMagicBytesLength; i += 1)
{
magicBytes[i] = binaryReader.ReadByte();
foreach (KeyValuePair<byte[], Func<BinaryReader, Size>> kvPair in _ImageFormatDecoders)
foreach (KeyValuePair<byte[], Func<BinaryReader, Size?>> kvPair in _ImageFormatDecoders)
{
if (StartsWith(magicBytes, kvPair.Key))
{
return kvPair.Value(binaryReader);
}
}
}
throw new ArgumentException(_ErrorMessage, nameof(binaryReader));
return null;
}
/// <summary>
/// Gets the dimensions of an image.
///internal </summary>
/// <param name="path">The path of the image to get the dimensions of.</param>
/// <returns>The dimensions of the specified image.</returns>
/// <exception cref="ArgumentException">The image was of an unrecognized format.</exception>
internal static Size GetDimensions(string path)
internal static Size? GetDimensions(string path)
{
using BinaryReader binaryReader = new(File.OpenRead(path));
try
{
return GetDimensions(binaryReader);
}
catch (ArgumentException e)
{
if (e.Message.StartsWith(_ErrorMessage))
{
throw new ArgumentException(_ErrorMessage, nameof(path), e);
}
else
{
throw;
}
}
return GetDimensions(binaryReader);
}
internal static Size? GetDimensions(Stream stream)
{
using BinaryReader binaryReader = new(stream);
return GetDimensions(binaryReader);
}
}

View File

@ -1,5 +1,6 @@
using MetadataExtractor;
using MetadataExtractor.Formats.Exif;
using MetadataExtractor.Formats.Exif.Makernotes;
using System.Globalization;
namespace View_by_Distance.Metadata.Models.Stateless;
@ -23,344 +24,545 @@ internal abstract class Exif
return result;
}
private static Shared.Models.AviDirectory GetAviDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
private static Shared.Models.AviDirectory[] GetAviDirectories(IReadOnlyList<MetadataExtractor.Directory> directories)
{
Shared.Models.AviDirectory result;
MetadataExtractor.Formats.Avi.AviDirectory? aviDirectory = directories.OfType<MetadataExtractor.Formats.Avi.AviDirectory>().FirstOrDefault();
if (aviDirectory is null)
result = new(null, null, null, null);
else
List<Shared.Models.AviDirectory> results = [];
Shared.Models.AviDirectory aviDirectory;
IEnumerable<MetadataExtractor.Formats.Avi.AviDirectory> aviDirectories = directories.OfType<MetadataExtractor.Formats.Avi.AviDirectory>();
foreach (MetadataExtractor.Formats.Avi.AviDirectory a in aviDirectories)
{
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));
result = new(dateTimeOriginal, duration, height, width);
dateTimeOriginal = GetDateTime(a.GetString(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal));
if (dateTimeOriginal is null && duration is null && height is null && width is null)
continue;
aviDirectory = new(dateTimeOriginal, duration, height, width);
results.Add(aviDirectory);
}
return result;
return results.ToArray();
}
private static Shared.Models.ExifDirectoryBase GetExifDirectoryBase(IReadOnlyList<MetadataExtractor.Directory> directories)
private static Shared.Models.ExifDirectoryBase[] GetExifBaseDirectories(IReadOnlyList<MetadataExtractor.Directory> directories)
{
Shared.Models.ExifDirectoryBase result;
ExifDirectoryBase? exifDirectoryBase = directories.OfType<ExifDirectoryBase>().FirstOrDefault();
if (exifDirectoryBase is null)
result = new(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
else
List<Shared.Models.ExifDirectoryBase> results = [];
Shared.Models.ExifDirectoryBase exifDirectoryBase;
IEnumerable<ExifDirectoryBase> exifBaseDirectories = directories.OfType<ExifDirectoryBase>();
foreach (ExifDirectoryBase e in exifBaseDirectories)
{
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));
result = 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);
dateTimeDigitized = GetDateTime(e.GetString(ExifDirectoryBase.TagDateTimeDigitized));
if (userComment is not null && userComment.Length > 255)
userComment = "...";
if (aperture is null
&& applicationNotes is null
&& artist is null
&& bitsPerSample is null
&& bodySerialNumber is null
&& cameraOwnerName is null
&& compressedAverageBitsPerPixel is null
&& compression is null
&& copyright is null
&& dateTime is null
&& dateTimeDigitized is null
&& dateTimeOriginal is null
&& documentName is null
&& exifVersion is null
&& exposureTime is null
&& fileSource is null
&& imageDescription is null
&& imageHeight is null
&& imageNumber is null
&& imageUniqueId is null
&& imageWidth is null
&& isoSpeed is null
&& lensMake is null
&& lensModel is null
&& lensSerialNumber is null
&& make is null
&& makerNote is null
&& model is null
&& orientation is null
&& orientationValue is null
&& rating is null
&& ratingPercent is null
&& securityClassification is null
&& shutterSpeed is null
&& software is null
&& timeZone is null
&& timeZoneDigitized is null
&& timeZoneOriginal is null
&& userComment is null
&& winAuthor is null
&& winComment is null
&& winKeywords is null
&& winSubject is null
&& winTitle is null
&& xResolution is not null
&& yResolution is null)
continue;
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 result;
return results.ToArray();
}
private static Shared.Models.FileMetadataDirectory GetFileMetadataDirectory(string file, IReadOnlyList<MetadataExtractor.Directory> directories)
private static Shared.Models.FileMetadataDirectory[] GetFileMetadataDirectories(string file, IReadOnlyList<MetadataExtractor.Directory> directories)
{
Shared.Models.FileMetadataDirectory result;
MetadataExtractor.Formats.FileSystem.FileMetadataDirectory? fileMetadataDirectory = directories.OfType<MetadataExtractor.Formats.FileSystem.FileMetadataDirectory>().FirstOrDefault();
if (fileMetadataDirectory is null)
result = new(null, null, null);
else
List<Shared.Models.FileMetadataDirectory> results = [];
Shared.Models.FileMetadataDirectory fileMetadataDirectory;
IEnumerable<MetadataExtractor.Formats.FileSystem.FileMetadataDirectory> fileMetadataDirectories = directories.OfType<MetadataExtractor.Formats.FileSystem.FileMetadataDirectory>();
foreach (MetadataExtractor.Formats.FileSystem.FileMetadataDirectory f in fileMetadataDirectories)
{
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})");
result = new(fileModifiedDate, fileName, fileSize);
if (fileModifiedDate is null && fileName is null && fileSize is null)
continue;
fileMetadataDirectory = new(fileModifiedDate, fileName, fileSize);
results.Add(fileMetadataDirectory);
}
return result;
return results.ToArray();
}
private static Shared.Models.GifHeaderDirectory GetGifHeaderDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
private static Shared.Models.GifHeaderDirectory[] GetGifHeaderDirectories(IReadOnlyList<MetadataExtractor.Directory> directories)
{
Shared.Models.GifHeaderDirectory result;
MetadataExtractor.Formats.Gif.GifHeaderDirectory? gifHeaderDirectory = directories.OfType<MetadataExtractor.Formats.Gif.GifHeaderDirectory>().FirstOrDefault();
if (gifHeaderDirectory is null)
result = new(null, null);
else
List<Shared.Models.GifHeaderDirectory> results = [];
Shared.Models.GifHeaderDirectory gifHeaderDirectory;
IEnumerable<MetadataExtractor.Formats.Gif.GifHeaderDirectory> gifHeaderDirectories = directories.OfType<MetadataExtractor.Formats.Gif.GifHeaderDirectory>();
foreach (MetadataExtractor.Formats.Gif.GifHeaderDirectory g in gifHeaderDirectories)
{
string? imageHeight = gifHeaderDirectory.GetDescription(MetadataExtractor.Formats.Gif.GifHeaderDirectory.TagImageHeight);
string? imageWidth = gifHeaderDirectory.GetDescription(MetadataExtractor.Formats.Gif.GifHeaderDirectory.TagImageWidth);
result = new(imageHeight, imageWidth);
if (g.Tags.Count == 0)
continue;
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;
gifHeaderDirectory = new(imageHeight, imageWidth);
results.Add(gifHeaderDirectory);
}
return result;
return results.ToArray();
}
private static Shared.Models.PhotoshopDirectory GetPhotoshopDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
private static Shared.Models.GpsDirectory[] GetGpsDirectories(IReadOnlyList<MetadataExtractor.Directory> directories)
{
Shared.Models.PhotoshopDirectory result;
MetadataExtractor.Formats.Photoshop.PhotoshopDirectory? PhotoshopDirectory = directories.OfType<MetadataExtractor.Formats.Photoshop.PhotoshopDirectory>().FirstOrDefault();
if (PhotoshopDirectory is null)
result = new(null, null);
else
{
string? jpegQuality = PhotoshopDirectory.GetDescription(MetadataExtractor.Formats.Photoshop.PhotoshopDirectory.TagJpegQuality);
string? url = PhotoshopDirectory.GetDescription(MetadataExtractor.Formats.Photoshop.PhotoshopDirectory.TagUrl);
result = new(jpegQuality, url);
}
return result;
}
private static Shared.Models.GpsDirectory GetGpsDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
{
Shared.Models.GpsDirectory result;
GpsDirectory? gpsDirectory = directories.OfType<GpsDirectory>().FirstOrDefault();
if (gpsDirectory is null)
result = new(null, null, null, null, null, null);
else
List<Shared.Models.GpsDirectory> results = [];
Shared.Models.GpsDirectory gpsDirectory;
IEnumerable<GpsDirectory> gpsDirectories = directories.OfType<GpsDirectory>();
foreach (GpsDirectory g in gpsDirectories)
{
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));
result = new(altitude,
latitude,
latitudeRef,
longitude,
longitudeRef,
timeStamp);
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;
gpsDirectory = new(altitude,
latitude,
latitudeRef,
longitude,
longitudeRef,
timeStamp);
results.Add(gpsDirectory);
}
return result;
return results.ToArray();
}
private static Shared.Models.JpegDirectory GetJpegDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
private static Shared.Models.JpegDirectory[] GetJpegDirectories(IReadOnlyList<MetadataExtractor.Directory> directories)
{
Shared.Models.JpegDirectory result;
MetadataExtractor.Formats.Jpeg.JpegDirectory? jpegDirectory = directories.OfType<MetadataExtractor.Formats.Jpeg.JpegDirectory>().FirstOrDefault();
if (jpegDirectory is null)
result = new(null, null);
else
List<Shared.Models.JpegDirectory> results = [];
Shared.Models.JpegDirectory jpegDirectory;
IEnumerable<MetadataExtractor.Formats.Jpeg.JpegDirectory> jpegDirectories = directories.OfType<MetadataExtractor.Formats.Jpeg.JpegDirectory>();
foreach (MetadataExtractor.Formats.Jpeg.JpegDirectory j in jpegDirectories)
{
string? imageHeight = jpegDirectory.GetDescription(MetadataExtractor.Formats.Jpeg.JpegDirectory.TagImageHeight);
string? imageWidth = jpegDirectory.GetDescription(MetadataExtractor.Formats.Jpeg.JpegDirectory.TagImageWidth);
result = new(imageHeight, imageWidth);
if (j.Tags.Count == 0)
continue;
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;
jpegDirectory = new(imageHeight, imageWidth);
results.Add(jpegDirectory);
}
return result;
return results.ToArray();
}
private static Shared.Models.PngDirectory GetPngDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
private static Shared.Models.MakernoteDirectory[] GetMakernoteDirectories(IReadOnlyList<MetadataExtractor.Directory> directories)
{
Shared.Models.PngDirectory result;
MetadataExtractor.Formats.Png.PngDirectory? pngDirectory = directories.OfType<MetadataExtractor.Formats.Png.PngDirectory>().FirstOrDefault();
if (pngDirectory is null)
result = new(null, null);
else
List<Shared.Models.MakernoteDirectory> results = [];
Shared.Models.MakernoteDirectory makernoteDirectory;
IEnumerable<AppleMakernoteDirectory> appleMakernoteDirectories = directories.OfType<AppleMakernoteDirectory>();
foreach (AppleMakernoteDirectory appleMakernoteDirectory in appleMakernoteDirectories)
{
string? imageHeight = pngDirectory.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagImageHeight);
string? imageWidth = pngDirectory.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagImageWidth);
result = new(imageHeight, imageWidth);
if (appleMakernoteDirectory.Tags.Count == 0)
continue;
string? cameraSerialNumber = null;
string? firmwareVersion = null;
string? qualityAndFileFormat = null;
if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null)
continue;
makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat);
results.Add(makernoteDirectory);
}
return result;
IEnumerable<CanonMakernoteDirectory> canonMakernoteDirectories = directories.OfType<CanonMakernoteDirectory>();
foreach (CanonMakernoteDirectory canonMakernoteDirectory in canonMakernoteDirectories)
{
if (canonMakernoteDirectory.Tags.Count == 0)
continue;
string? cameraSerialNumber = canonMakernoteDirectory.GetDescription(CanonMakernoteDirectory.TagModelId);
string? firmwareVersion = canonMakernoteDirectory.GetDescription(CanonMakernoteDirectory.TagCanonFirmwareVersion);
string? qualityAndFileFormat = canonMakernoteDirectory.GetDescription(CanonMakernoteDirectory.CameraSettings.TagQuality);
if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null)
continue;
makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat);
results.Add(makernoteDirectory);
}
IEnumerable<NikonType2MakernoteDirectory> nikonType2MakernoteDirectories = directories.OfType<NikonType2MakernoteDirectory>();
foreach (NikonType2MakernoteDirectory nikonType2MakernoteDirectory in nikonType2MakernoteDirectories)
{
if (nikonType2MakernoteDirectory.Tags.Count == 0)
continue;
string? cameraSerialNumber = nikonType2MakernoteDirectory.GetDescription(NikonType2MakernoteDirectory.TagCameraSerialNumber);
string? firmwareVersion = nikonType2MakernoteDirectory.GetDescription(NikonType2MakernoteDirectory.TagFirmwareVersion);
string? qualityAndFileFormat = nikonType2MakernoteDirectory.GetDescription(NikonType2MakernoteDirectory.TagQualityAndFileFormat);
if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null)
continue;
makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat);
results.Add(makernoteDirectory);
}
IEnumerable<OlympusMakernoteDirectory> olympusMakernoteDirectories = directories.OfType<OlympusMakernoteDirectory>();
foreach (OlympusMakernoteDirectory olympusMakernoteDirectory in olympusMakernoteDirectories)
{
if (olympusMakernoteDirectory.Tags.Count == 0)
continue;
string? cameraSerialNumber = olympusMakernoteDirectory.GetDescription(OlympusMakernoteDirectory.TagSerialNumber1);
string? firmwareVersion = olympusMakernoteDirectory.GetDescription(OlympusMakernoteDirectory.TagBodyFirmwareVersion);
string? qualityAndFileFormat = olympusMakernoteDirectory.GetDescription(OlympusMakernoteDirectory.TagJpegQuality);
if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null)
continue;
makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat);
results.Add(makernoteDirectory);
}
IEnumerable<PanasonicMakernoteDirectory> panasonicMakernoteDirectories = directories.OfType<PanasonicMakernoteDirectory>();
foreach (PanasonicMakernoteDirectory panasonicMakernoteDirectory in panasonicMakernoteDirectories)
{
if (panasonicMakernoteDirectory.Tags.Count == 0)
continue;
string? cameraSerialNumber = panasonicMakernoteDirectory.GetDescription(PanasonicMakernoteDirectory.TagInternalSerialNumber);
string? firmwareVersion = panasonicMakernoteDirectory.GetDescription(PanasonicMakernoteDirectory.TagFirmwareVersion);
string? qualityAndFileFormat = panasonicMakernoteDirectory.GetDescription(PanasonicMakernoteDirectory.TagQualityMode);
if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null)
continue;
makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat);
results.Add(makernoteDirectory);
}
IEnumerable<SamsungType2MakernoteDirectory> samsungType2MakernoteDirectories = directories.OfType<SamsungType2MakernoteDirectory>();
foreach (SamsungType2MakernoteDirectory samsungType2MakernoteDirectory in samsungType2MakernoteDirectories)
{
if (samsungType2MakernoteDirectory.Tags.Count == 0)
continue;
string? cameraSerialNumber = samsungType2MakernoteDirectory.GetDescription(SamsungType2MakernoteDirectory.TagSerialNumber);
string? firmwareVersion = samsungType2MakernoteDirectory.GetDescription(SamsungType2MakernoteDirectory.TagFirmwareName);
string? qualityAndFileFormat = null;
if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null)
continue;
makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat);
results.Add(makernoteDirectory);
}
IEnumerable<SonyType6MakernoteDirectory> sonyType6MakernoteDirectories = directories.OfType<SonyType6MakernoteDirectory>();
foreach (SonyType6MakernoteDirectory sonyType6MakernoteDirectory in sonyType6MakernoteDirectories)
{
if (sonyType6MakernoteDirectory.Tags.Count == 0)
continue;
string? cameraSerialNumber = null;
string? firmwareVersion = null;
string? qualityAndFileFormat = null;
if (cameraSerialNumber is null && firmwareVersion is null && qualityAndFileFormat is null)
continue;
makernoteDirectory = new(cameraSerialNumber, firmwareVersion, qualityAndFileFormat);
results.Add(makernoteDirectory);
}
return results.ToArray();
}
private static Shared.Models.QuickTimeMovieHeaderDirectory GetQuickTimeMovieHeaderDirectoryDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
private static Shared.Models.PhotoshopDirectory[] GetPhotoshopDirectories(IReadOnlyList<MetadataExtractor.Directory> directories)
{
Shared.Models.QuickTimeMovieHeaderDirectory result;
MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory? aviDirectory = directories.OfType<MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory>().FirstOrDefault();
if (aviDirectory is null)
result = new(null);
else
List<Shared.Models.PhotoshopDirectory> results = [];
Shared.Models.PhotoshopDirectory photoshopDirectory;
IEnumerable<MetadataExtractor.Formats.Photoshop.PhotoshopDirectory> photoshopDirectories = directories.OfType<MetadataExtractor.Formats.Photoshop.PhotoshopDirectory>();
foreach (MetadataExtractor.Formats.Photoshop.PhotoshopDirectory p in photoshopDirectories)
{
if (p.Tags.Count == 0)
continue;
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;
photoshopDirectory = new(jpegQuality, url);
results.Add(photoshopDirectory);
}
return results.ToArray();
}
private static Shared.Models.PngDirectory[] GetPngDirectories(IReadOnlyList<MetadataExtractor.Directory> directories)
{
List<Shared.Models.PngDirectory> results = [];
Shared.Models.PngDirectory pngDirectory;
IEnumerable<MetadataExtractor.Formats.Png.PngDirectory> pngDirectories = directories.OfType<MetadataExtractor.Formats.Png.PngDirectory>();
foreach (MetadataExtractor.Formats.Png.PngDirectory p in pngDirectories)
{
if (p.Tags.Count == 0)
continue;
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;
pngDirectory = new(imageHeight, imageWidth, textualData);
results.Add(pngDirectory);
}
return results.ToArray();
}
private static Shared.Models.QuickTimeMovieHeaderDirectory[] GetQuickTimeMovieHeaderDirectoryDirectories(IReadOnlyList<MetadataExtractor.Directory> directories)
{
List<Shared.Models.QuickTimeMovieHeaderDirectory> results = [];
Shared.Models.QuickTimeMovieHeaderDirectory quickTimeMovieHeaderDirectory;
IEnumerable<MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory> quickTimeMovieHeaderDirectories = directories.OfType<MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory>();
foreach (MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory q in quickTimeMovieHeaderDirectories)
{
if (q.Tags.Count == 0)
continue;
DateTime? created;
if (aviDirectory.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(aviDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated));
result = new(created);
created = GetDateTime(q.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated));
if (created is null)
continue;
quickTimeMovieHeaderDirectory = new(created);
results.Add(quickTimeMovieHeaderDirectory);
}
return result;
return results.ToArray();
}
private static Shared.Models.QuickTimeTrackHeaderDirectory GetQuickTimeTrackHeaderDirectoryDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
private static Shared.Models.QuickTimeTrackHeaderDirectory[] GetQuickTimeTrackHeaderDirectoryDirectories(IReadOnlyList<MetadataExtractor.Directory> directories)
{
Shared.Models.QuickTimeTrackHeaderDirectory result;
MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory? aviDirectory = directories.OfType<MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory>().FirstOrDefault();
if (aviDirectory is null)
result = new(null);
else
List<Shared.Models.QuickTimeTrackHeaderDirectory> results = [];
Shared.Models.QuickTimeTrackHeaderDirectory quickTimeTrackHeaderDirectory;
IEnumerable<MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory> quickTimeTrackHeaderDirectories = directories.OfType<MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory>();
foreach (MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory q in quickTimeTrackHeaderDirectories)
{
if (q.Tags.Count == 0)
continue;
DateTime? created;
if (aviDirectory.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(aviDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated));
result = new(created);
created = GetDateTime(q.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated));
if (created is null)
continue;
quickTimeTrackHeaderDirectory = new(created);
results.Add(quickTimeTrackHeaderDirectory);
}
return results.ToArray();
}
private static Shared.Models.WebPDirectory[] GetWebPDirectories(IReadOnlyList<MetadataExtractor.Directory> directories)
{
List<Shared.Models.WebPDirectory> results = [];
Shared.Models.WebPDirectory webPDirectory;
IEnumerable<MetadataExtractor.Formats.WebP.WebPDirectory> webPDirectories = directories.OfType<MetadataExtractor.Formats.WebP.WebPDirectory>();
foreach (MetadataExtractor.Formats.WebP.WebPDirectory w in webPDirectories)
{
if (w.Tags.Count == 0)
continue;
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;
webPDirectory = new(imageHeight, imageWidth);
results.Add(webPDirectory);
}
return results.ToArray();
}
private static Shared.Models.ExifDirectory Covert(Shared.Models.FilePath filePath, Shared.Models.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName, IReadOnlyList<MetadataExtractor.Directory> directories, System.Drawing.Size? size)
{
Shared.Models.ExifDirectory result;
Shared.Models.AviDirectory[] aviDirectories = GetAviDirectories(directories);
Shared.Models.GpsDirectory[] gpsDirectories = GetGpsDirectories(directories);
Shared.Models.PngDirectory[] pngDirectories = GetPngDirectories(directories);
Shared.Models.JpegDirectory[] jpegDirectories = GetJpegDirectories(directories);
Shared.Models.WebPDirectory[] webPDirectories = GetWebPDirectories(directories);
Shared.Models.ExifDirectoryBase[] exifBaseDirectories = GetExifBaseDirectories(directories);
Shared.Models.GifHeaderDirectory[] gifHeaderDirectories = GetGifHeaderDirectories(directories);
Shared.Models.MakernoteDirectory[] MakernoteDirectories = GetMakernoteDirectories(directories);
Shared.Models.PhotoshopDirectory[] photoshopDirectories = GetPhotoshopDirectories(directories);
Shared.Models.FileMetadataDirectory[] fileMetadataDirectories = GetFileMetadataDirectories(filePath.FullName, directories);
Shared.Models.QuickTimeMovieHeaderDirectory[] quickTimeMovieHeaderDirectories = GetQuickTimeMovieHeaderDirectoryDirectories(directories);
Shared.Models.QuickTimeTrackHeaderDirectory[] quickTimeTrackHeaderDirectories = GetQuickTimeTrackHeaderDirectoryDirectories(directories);
result = new(aviDirectories,
null,
exifBaseDirectories,
fileMetadataDirectories,
filePath,
gifHeaderDirectories,
gpsDirectories,
size?.Height,
jpegDirectories,
MakernoteDirectories,
personKeyFormattedAndKeyTicksAndDisplayDirectoryName,
photoshopDirectories,
pngDirectories,
quickTimeMovieHeaderDirectories,
quickTimeTrackHeaderDirectories,
webPDirectories,
size?.Width);
return result;
}
private static Shared.Models.WebPDirectory GetWebPDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
internal static Shared.Models.ExifDirectory GetExifDirectory(Shared.Models.FilePath filePath, Stream stream, Shared.Models.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName)
{
Shared.Models.WebPDirectory result;
MetadataExtractor.Formats.WebP.WebPDirectory? WebPDirectory = directories.OfType<MetadataExtractor.Formats.WebP.WebPDirectory>().FirstOrDefault();
if (WebPDirectory is null)
result = new(null, null);
Shared.Models.ExifDirectory result;
IReadOnlyList<MetadataExtractor.Directory> directories = ImageMetadataReader.ReadMetadata(stream);
System.Drawing.Size? size;
if (!stream.CanSeek)
size = null;
else
{
string? imageHeight = WebPDirectory.GetDescription(MetadataExtractor.Formats.WebP.WebPDirectory.TagImageHeight);
string? imageWidth = WebPDirectory.GetDescription(MetadataExtractor.Formats.WebP.WebPDirectory.TagImageWidth);
result = new(imageHeight, imageWidth);
try
{ size = Dimensions.GetDimensions(stream); }
catch (Exception)
{ size = null; }
}
return result;
}
internal static Shared.Models.ExifDirectory Covert(Shared.Models.FilePath filePath, Shared.Models.DeterministicHashCode deterministicHashCode, System.Drawing.Size? size, IReadOnlyList<MetadataExtractor.Directory> directories)
{
Shared.Models.ExifDirectory results;
Shared.Models.AviDirectory aviDirectory = GetAviDirectory(directories);
Shared.Models.GpsDirectory gpsDirectory = GetGpsDirectory(directories);
Shared.Models.PngDirectory pngDirectory = GetPngDirectory(directories);
Shared.Models.JpegDirectory jpegDirectory = GetJpegDirectory(directories);
Shared.Models.WebPDirectory webPDirectory = GetWebPDirectory(directories);
Shared.Models.ExifDirectoryBase exifDirectoryBase = GetExifDirectoryBase(directories);
Shared.Models.GifHeaderDirectory gifHeaderDirectory = GetGifHeaderDirectory(directories);
Shared.Models.PhotoshopDirectory photoshopDirectory = GetPhotoshopDirectory(directories);
Shared.Models.FileMetadataDirectory fileMetadataDirectory = GetFileMetadataDirectory(filePath.FullName, directories);
Shared.Models.QuickTimeMovieHeaderDirectory quickTimeMovieHeaderDirectory = GetQuickTimeMovieHeaderDirectoryDirectory(directories);
Shared.Models.QuickTimeTrackHeaderDirectory quickTimeTrackHeaderDirectory = GetQuickTimeTrackHeaderDirectoryDirectory(directories);
results = new(aviDirectory,
exifDirectoryBase,
fileMetadataDirectory,
gifHeaderDirectory,
gpsDirectory,
size?.Height,
deterministicHashCode.Id ?? filePath.Id,
jpegDirectory,
filePath.Name,
photoshopDirectory,
pngDirectory,
quickTimeMovieHeaderDirectory,
quickTimeTrackHeaderDirectory,
webPDirectory,
size?.Width);
return results;
}
internal static string GetMaker(Shared.Models.ExifDirectoryBase exifDirectoryBase)
{
string result;
if (string.IsNullOrEmpty(exifDirectoryBase.Make))
result = "Unknown";
else
result = $"{exifDirectoryBase.Make[0].ToString().ToUpper()}{exifDirectoryBase.Make[1..]}".Trim();
result = Covert(filePath, personKeyFormattedAndKeyTicksAndDisplayDirectoryName, directories, size);
return result;
}

View File

@ -0,0 +1,53 @@
using View_by_Distance.Shared.Models;
namespace View_by_Distance.Metadata.Models.Stateless;
internal static class Face
{
internal static string? GetFaceEncoding(PngDirectory[]? pngDirectories)
{
string? result = null;
if (pngDirectories is not null)
{
const string comment = "Comment:";
foreach (PngDirectory pngDirectory in pngDirectories)
{
if (pngDirectory.TextualData is null || !pngDirectory.TextualData.StartsWith(comment))
continue;
result = pngDirectory.TextualData[comment.Length..];
break;
}
}
return result;
}
internal static string? GetOutputResolution(PngDirectory[]? pngDirectories)
{
string? result = null;
if (pngDirectories is not null)
{
const string artist = "Artist:";
foreach (PngDirectory pngDirectory in pngDirectories)
{
if (pngDirectory.TextualData is null || !pngDirectory.TextualData.StartsWith(artist))
continue;
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;
}
}

View File

@ -1,4 +1,6 @@
using View_by_Distance.Metadata.Models.Stateless.Methods;
using MetadataExtractor;
using View_by_Distance.Metadata.Models.Stateless;
using View_by_Distance.Shared.Models;
namespace View_by_Distance.Metadata.Models.Stateless;
@ -45,9 +47,9 @@ internal abstract class GPS
Math.Asin(Math.Min(1,
Math.Sqrt(
Math.Pow(Math.Sin(DiffRadian(originLatitude, destinationLatitude) / 2.0), 2.0) +
Math.Cos(ToRadian(originLatitude)) * Math.Cos(ToRadian(destinationLatitude)) *
(Math.Cos(ToRadian(originLatitude)) * Math.Cos(ToRadian(destinationLatitude)) *
Math.Pow(Math.Sin(DiffRadian(originLongitude, destinationLongitude) / 2.0),
2.0)))), decimalPlaces);
2.0))))), decimalPlaces);
}
private static double ParseValueFromDmsString(string value)
@ -85,34 +87,36 @@ internal abstract class GPS
return result;
}
// internal static GeoLocation? GeoLocation(ReadOnlyDictionary<string, MetadataExtractorDirectory> metadataExtractorDirectories)
// {
// GeoLocation? result;
// if (!metadataExtractorDirectories.TryGetValue("GPS", out MetadataExtractorDirectory? metadataExtractorDirectory))
// result = null;
// else
// {
// MetadataExtractorTag? metadataExtractorTag;
// if (!metadataExtractorDirectory.Tags.TryGetValue((int)Shared.Models.Stateless.IExif.Tags.GPSLatitude, out metadataExtractorTag) || string.IsNullOrEmpty(metadataExtractorTag.Description))
// result = null;
// else
// {
// string latitudeDMS = metadataExtractorTag.Description;
// double latitude = ParseValueFromDmsString(latitudeDMS);
// if (!metadataExtractorDirectory.Tags.TryGetValue((int)Shared.Models.Stateless.IExif.Tags.GPSLongitude, out metadataExtractorTag) || string.IsNullOrEmpty(metadataExtractorTag.Description))
// result = null;
// else
// {
// string longitudeDMS = metadataExtractorTag.Description;
// double longitude = ParseValueFromDmsString(longitudeDMS);
// result = new(latitude, longitude);
// string dms = result.ToDmsString();
// if ($"{latitudeDMS}, {longitudeDMS}" != dms)
// result = null;
// }
// }
// }
// return result;
// }
internal static GeoLocation? GeoLocation(GpsDirectory[]? gpsDirectories)
{
GeoLocation? result = null;
if (gpsDirectories is not null)
{
foreach (GpsDirectory gpsDirectory in gpsDirectories)
{
if (string.IsNullOrEmpty(gpsDirectory?.Latitude))
result = null;
else
{
string latitudeDMS = gpsDirectory.Latitude;
double latitude = ParseValueFromDmsString(latitudeDMS);
if (string.IsNullOrEmpty(gpsDirectory.Longitude))
result = null;
else
{
string longitudeDMS = gpsDirectory.Longitude;
double longitude = ParseValueFromDmsString(longitudeDMS);
result = new(latitude, longitude);
string dms = result.ToDmsString();
if ($"{latitudeDMS}, {longitudeDMS}" != dms)
result = null;
}
}
if (result is not null)
break;
}
}
return result;
}
}

View File

@ -0,0 +1,148 @@
using System.Collections.ObjectModel;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Properties;
using View_by_Distance.Shared.Models.Stateless;
namespace View_by_Distance.Metadata.Models.Stateless;
internal static class Get
{
internal static ReadOnlyDictionary<string, List<FileHolder>> GetKeyValuePairs(IEnumerable<string> files)
{
Dictionary<string, List<FileHolder>> results = [];
string key;
FileHolder fileHolder;
List<FileHolder>? fileHolders;
foreach (string file in files)
{
fileHolder = FileHolder.Get(file);
if (fileHolder.DirectoryFullPath is null)
continue;
key = $"{Path.Combine(fileHolder.DirectoryFullPath, fileHolder.NameWithoutExtension)}";
if (!results.TryGetValue(key, out fileHolders))
{
results.Add(key, []);
if (!results.TryGetValue(key, out fileHolders))
throw new NotImplementedException();
}
fileHolders.Add(fileHolder);
}
return results.AsReadOnly();
}
internal static ReadOnlyDictionary<string, List<FileHolder>> GetKeyValuePairs(IEnumerable<NginxFileSystem> collection)
{
Dictionary<string, List<FileHolder>> results = [];
string key;
FileHolder fileHolder;
List<FileHolder>? fileHolders;
foreach (NginxFileSystem nginxFileSystem in collection)
{
fileHolder = FileHolder.Get(nginxFileSystem);
if (fileHolder.DirectoryFullPath is null)
continue;
key = $"{Path.Combine(fileHolder.DirectoryFullPath, fileHolder.NameWithoutExtension)}";
if (!results.TryGetValue(key, out fileHolders))
{
results.Add(key, []);
if (!results.TryGetValue(key, out fileHolders))
throw new NotImplementedException();
}
fileHolders.Add(fileHolder);
}
return results.AsReadOnly();
}
internal static Action<string> SetExifDirectoryCollection(IRename rename, ResultSettings resultSettings, MetadataSettings metadataSettings, IRenameSettings renameSettings, A_Metadata metadata, List<string> distinct, List<MetadataGroup> metadataGroups)
{
return file =>
{
rename.Tick();
ExifDirectory exifDirectory;
MetadataGroup metadataGroup;
DeterministicHashCode deterministicHashCode;
FileHolder fileHolder = FileHolder.Get(file);
MinimumYearAndPathCombined minimumYearAndPathCombined;
bool fastForwardMovingPictureExpertsGroupUsed;
FilePath? fastForwardMovingPictureExpertsGroupFilePath;
ReadOnlyCollection<string>? fastForwardMovingPictureExpertsGroupFiles;
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 (renameSettings.SkipIdFiles && filePath.Id is not null && (filePath.IsIntelligentIdFormat || filePath.SortOrder is null))
return;
if (filePath.Id is not null)
{
fastForwardMovingPictureExpertsGroupFiles = null;
deterministicHashCode = new(null, filePath.Id, null);
}
else
{
fastForwardMovingPictureExpertsGroupFiles = rename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(renameSettings, filePath);
fastForwardMovingPictureExpertsGroupFilePath = fastForwardMovingPictureExpertsGroupFiles.Count == 0 ? null : FilePath.Get(resultSettings, metadataSettings, FileHolder.Get(fastForwardMovingPictureExpertsGroupFiles[0]), index: null);
deterministicHashCode = fastForwardMovingPictureExpertsGroupFilePath is null ? rename.GetDeterministicHashCode(filePath) : rename.GetDeterministicHashCode(fastForwardMovingPictureExpertsGroupFilePath);
}
filePath = FilePath.Get(filePath, deterministicHashCode);
fastForwardMovingPictureExpertsGroupUsed = fastForwardMovingPictureExpertsGroupFiles is not null && fastForwardMovingPictureExpertsGroupFiles.Count > 0;
(minimumYearAndPathCombined, exifDirectory) = metadata.GetMetadataCollection(resultSettings, metadataSettings, filePath);
metadataGroup = new(fastForwardMovingPictureExpertsGroupUsed, filePath, minimumYearAndPathCombined, exifDirectory, new([]));
lock (metadataGroups)
metadataGroups.Add(metadataGroup);
if (fastForwardMovingPictureExpertsGroupUsed && fastForwardMovingPictureExpertsGroupFiles is not null)
{
foreach (string fastForwardMovingPictureExpertsGroupFile in fastForwardMovingPictureExpertsGroupFiles)
File.Delete(fastForwardMovingPictureExpertsGroupFile);
}
};
}
internal static Action<string> SetExifDirectoryCollection(IWindows windows, ResultSettings resultSettings, MetadataSettings metadataSettings, IWindowsSettings windowsSettings, A_Metadata metadata, List<string> distinct, List<MetadataGroup> metadataGroups)
{
return file =>
{
windows.Tick();
ExifDirectory exifDirectory;
MetadataGroup metadataGroup;
HttpClient? httpClient = null;
DeterministicHashCode deterministicHashCode;
FileHolder fileHolder = FileHolder.Get(file);
MinimumYearAndPathCombined minimumYearAndPathCombined;
bool fastForwardMovingPictureExpertsGroupUsed;
FilePath? fastForwardMovingPictureExpertsGroupFilePath;
ReadOnlyCollection<string>? fastForwardMovingPictureExpertsGroupFiles;
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 (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 = windows.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(windowsSettings, httpClient, filePath);
fastForwardMovingPictureExpertsGroupFilePath = fastForwardMovingPictureExpertsGroupFiles.Count == 0 ? null : FilePath.Get(resultSettings, metadataSettings, FileHolder.Get(fastForwardMovingPictureExpertsGroupFiles[0]), index: null);
deterministicHashCode = fastForwardMovingPictureExpertsGroupFilePath is null ? windows.GetDeterministicHashCode(httpClient, filePath) : windows.GetDeterministicHashCode(httpClient, fastForwardMovingPictureExpertsGroupFilePath);
}
filePath = FilePath.Get(filePath, deterministicHashCode);
fastForwardMovingPictureExpertsGroupUsed = fastForwardMovingPictureExpertsGroupFiles is not null && fastForwardMovingPictureExpertsGroupFiles.Count > 0;
(minimumYearAndPathCombined, exifDirectory) = metadata.GetMetadataCollection(resultSettings, metadataSettings, filePath);
metadataGroup = new(fastForwardMovingPictureExpertsGroupUsed, filePath, minimumYearAndPathCombined, exifDirectory, new([]));
lock (metadataGroups)
metadataGroups.Add(metadataGroup);
if (fastForwardMovingPictureExpertsGroupUsed && fastForwardMovingPictureExpertsGroupFiles is not null)
{
foreach (string fastForwardMovingPictureExpertsGroupFile in fastForwardMovingPictureExpertsGroupFiles)
File.Delete(fastForwardMovingPictureExpertsGroupFile);
}
}
};
}
}

View File

@ -0,0 +1,80 @@
using MetadataExtractor;
using System.Collections.ObjectModel;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Properties;
using View_by_Distance.Shared.Models.Stateless;
namespace View_by_Distance.Metadata.Models.Stateless;
public interface IMetadata
{
enum DistanceUnit
{
Miles,
NauticalMiles,
Kilometers,
Meters
}
ExifDirectory TestStatic_GetExifDirectory(FilePath filePath, Stream stream, PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName) =>
GetExifDirectory(filePath, stream, personKeyFormattedAndKeyTicksAndDisplayDirectoryName);
static ExifDirectory GetExifDirectory(FilePath filePath, Stream stream, PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName) =>
Exif.GetExifDirectory(filePath, stream, personKeyFormattedAndKeyTicksAndDisplayDirectoryName);
string? TestStatic_GetMaker(ExifDirectory? exifDirectory) =>
GetMaker(exifDirectory);
static string? GetMaker(ExifDirectory? exifDirectory) =>
Base.GetMaker(exifDirectory?.ExifBaseDirectories);
string? TestStatic_GetModel(ExifDirectory? exifDirectory) =>
GetModel(exifDirectory);
static string? GetModel(ExifDirectory? exifDirectory) =>
Base.GetModel(exifDirectory?.ExifBaseDirectories);
ReadOnlyCollection<string> TestStatic_GetKeywords(ExifDirectory? exifDirectory) =>
GetKeywords(exifDirectory);
static ReadOnlyCollection<string> GetKeywords(ExifDirectory? exifDirectory) =>
Base.GetKeywords(exifDirectory?.ExifBaseDirectories);
string? TestStatic_GetOutputResolution(ExifDirectory? exifDirectory) =>
GetOutputResolution(exifDirectory);
static string? GetOutputResolution(ExifDirectory? exifDirectory) =>
Face.GetOutputResolution(exifDirectory?.PngDirectories);
string? TestStatic_GetFaceEncoding(ExifDirectory? exifDirectory) =>
GetFaceEncoding(exifDirectory);
static string? GetFaceEncoding(ExifDirectory? exifDirectory) =>
Face.GetFaceEncoding(exifDirectory?.PngDirectories);
GeoLocation? TestStatic_GeoLocation(ExifDirectory? exifDirectory) =>
GeoLocation(exifDirectory);
static GeoLocation? GeoLocation(ExifDirectory? exifDirectory) =>
GPS.GeoLocation(exifDirectory?.GpsDirectories);
double? TestStatic_GetDistance(double originLatitude, double originLongitude, double destinationLatitude, double destinationLongitude, int decimalPlaces = 1, DistanceUnit distanceUnit = DistanceUnit.Miles) =>
GetDistance(originLatitude, originLongitude, destinationLatitude, destinationLongitude, decimalPlaces, distanceUnit);
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<string> TestStatic_SetExifDirectoryCollection(IRename rename, ResultSettings resultSettings, MetadataSettings metadataSettings, IRenameSettings renameSettings, A_Metadata metadata, List<string> distinct, List<MetadataGroup> metadataGroups) =>
SetExifDirectoryCollection(rename, resultSettings, metadataSettings, renameSettings, metadata, distinct, metadataGroups);
static Action<string> SetExifDirectoryCollection(IRename rename, ResultSettings resultSettings, MetadataSettings metadataSettings, IRenameSettings renameSettings, A_Metadata metadata, List<string> distinct, List<MetadataGroup> metadataGroups) =>
Get.SetExifDirectoryCollection(rename, resultSettings, metadataSettings, renameSettings, metadata, distinct, metadataGroups);
ReadOnlyDictionary<string, List<FileHolder>> TestStatic_GetKeyValuePairs(IEnumerable<string> files) =>
GetKeyValuePairs(files);
static ReadOnlyDictionary<string, List<FileHolder>> GetKeyValuePairs(IEnumerable<string> files) =>
Get.GetKeyValuePairs(files);
ReadOnlyDictionary<string, List<FileHolder>> TestStatic_GetKeyValuePairs(IEnumerable<NginxFileSystem> collection) =>
GetKeyValuePairs(collection);
static ReadOnlyDictionary<string, List<FileHolder>> GetKeyValuePairs(IEnumerable<NginxFileSystem> collection) =>
Get.GetKeyValuePairs(collection);
Action<string> TestStatic_SetExifDirectoryCollection(IWindows windows, ResultSettings resultSettings, MetadataSettings metadataSettings, IWindowsSettings windowsSettings, A_Metadata metadata, List<string> distinct, List<MetadataGroup> metadataGroups) =>
SetExifDirectoryCollection(windows, resultSettings, metadataSettings, windowsSettings, metadata, distinct, metadataGroups);
static Action<string> SetExifDirectoryCollection(IWindows windows, ResultSettings resultSettings, MetadataSettings metadataSettings, IWindowsSettings windowsSettings, A_Metadata metadata, List<string> distinct, List<MetadataGroup> metadataGroups) =>
Get.SetExifDirectoryCollection(windows, resultSettings, metadataSettings, windowsSettings, metadata, distinct, metadataGroups);
}

View File

@ -1,24 +0,0 @@
namespace View_by_Distance.Metadata.Models.Stateless.Methods;
public interface IMetadata
{
enum DistanceUnit
{
Miles,
NauticalMiles,
Kilometers,
Meters
}
string TestStatic_GetMaker(Shared.Models.ExifDirectoryBase exifDirectoryBase) =>
GetMaker(exifDirectoryBase);
static string GetMaker(Shared.Models.ExifDirectoryBase exifDirectoryBase) =>
Exif.GetMaker(exifDirectoryBase);
double? TestStatic_GetDistance(double originLatitude, double originLongitude, double destinationLatitude, double destinationLongitude, int decimalPlaces = 1, DistanceUnit distanceUnit = DistanceUnit.Miles) =>
GetDistance(originLatitude, originLongitude, destinationLatitude, destinationLongitude, decimalPlaces, distanceUnit);
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);
}

1
People/.vscode/read-me.md vendored Normal file
View File

@ -0,0 +1 @@
# Read Me

49
People/AA.People.csproj Normal file
View File

@ -0,0 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>library</OutputType>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<PackageId>Phares.AA.People</PackageId>
<Version>8.0.112.0</Version>
<Company>Phares</Company>
<Authors>Mike Phares</Authors>
<IncludeSymbols>true</IncludeSymbols>
<PackageReadmeFile>read-me.md</PackageReadmeFile>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>Windows</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)'=='true'">
<DefineConstants>OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>Linux</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<None Include=".vscode\read-me.md" Pack="true" PackagePath="\"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="MetadataExtractor" Version="2.8.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="WindowsShortcutFactory" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Metadata\AA.Metadata.csproj" />
<ProjectReference Include="..\Shared\AA.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,3 @@
namespace View_by_Distance.Metadata.Models;
public class A2_People() { }

View File

@ -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;
public interface IPeople
{
ReadOnlyCollection<long> TestStatic_GetPersonKeys(ReadOnlyCollection<PersonContainer> personContainers) =>
GetPersonKeys(personContainers);
static ReadOnlyCollection<long> GetPersonKeys(ReadOnlyCollection<PersonContainer> personContainers) =>
People.GetPersonKeys(personContainers);
ReadOnlyCollection<PersonContainer> TestStatic_GetPersonContainers(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, ICompareSettings compareSettings) =>
GetPersonContainers(resultSettings, metadataSettings, peopleSettings, compareSettings);
static ReadOnlyCollection<PersonContainer> 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<PersonKeyFormattedAndPersonBirthday> personKeyFormattedAndPersonBirthdayCollection) =>
VerifyAge(numberSign, personDisplayDirectory, minusOne, personDisplayDirectoryName, approximateYears, personKeyFormattedAndPersonBirthdayCollection);
static string? VerifyAge(char numberSign, string personDisplayDirectory, string? minusOne, string personDisplayDirectoryName, int? approximateYears, List<PersonKeyFormattedAndPersonBirthday> personKeyFormattedAndPersonBirthdayCollection) =>
People.VerifyAge(numberSign, personDisplayDirectory, minusOne, personDisplayDirectoryName, approximateYears, personKeyFormattedAndPersonBirthdayCollection);
static ReadOnlyCollection<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> TestStatic_GetJLinkDirectories(ResultSettings resultSettings, PeopleSettings peopleSettings) =>
GetJLinkDirectories(resultSettings, peopleSettings);
static ReadOnlyCollection<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> GetJLinkDirectories(ResultSettings resultSettings, PeopleSettings peopleSettings) =>
People.GetJLinkDirectories(resultSettings, peopleSettings);
static ReadOnlyCollection<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> TestStatic_GetJLinkResolvedDirectories(ResultSettings resultSettings, PeopleSettings peopleSettings) =>
GetJLinkResolvedDirectories(resultSettings, peopleSettings);
static ReadOnlyCollection<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> GetJLinkResolvedDirectories(ResultSettings resultSettings, PeopleSettings peopleSettings) =>
People.GetJLinkResolvedDirectories(resultSettings, peopleSettings);
static ReadOnlyCollections TestStatic_GetReadOnlyCollections(ResultSettings resultSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ReadOnlyCollection<PersonContainer> personContainers, ReadOnlyCollection<long> personKeys) =>
GetReadOnlyCollections(resultSettings, peopleSettings, distanceSettings, compareSettings, personContainers, personKeys);
static ReadOnlyCollections GetReadOnlyCollections(ResultSettings resultSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ReadOnlyCollection<PersonContainer> personContainers, ReadOnlyCollection<long> personKeys) =>
ReadOnlyCollectionsLogic.GetReadOnlyCollections(resultSettings, peopleSettings, distanceSettings, compareSettings, personContainers, personKeys);
}

View File

@ -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;
using WindowsShortcutFactory;
namespace View_by_Distance.People.Models.Stateless;
internal static class People
{
private record RecordA(List<string?> Changes, List<PersonContainer> PersonContainers);
private record RecordB(string Directory, string DirectoryName, string DirectoryNameSplitFirst);
internal static List<PersonKeyFormattedAndPersonBirthday> GetPersonBirthdays(string personBirthdayFormat, string[] personKeyDirectories, string personDisplayDirectoryName)
{
List<PersonKeyFormattedAndPersonBirthday> 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<PersonContainer> GetPersonContainersCollections(ResultSettings resultSettings, MetadataSettings metadataSettings, PersonDirectory personDirectory, char numberSign, string personDisplayDirectory, string personDisplayDirectoryName, int? approximateYears, List<PersonKeyFormattedAndPersonBirthday> personKeyFormattedAndPersonBirthdayCollection)
{
List<PersonContainer> results = [];
long personKey;
string[] files;
FilePath filePath;
const int zero = 0;
string personKeyDirectory;
FileHolder fileHolder;
PersonContainer personContainer;
PersonBirthday[] orderedPersonBirthdays;
List<FilePath> 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<FilePath> GetFilePaths(ResultSettings resultSettings, MetadataSettings metadataSettings, string personDisplayDirectory)
{
List<FilePath> results = [];
string[] files;
string checkFile;
FilePath filePath;
string directoryName;
FileHolder fileHolder;
List<string> 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<FilePath> GetFilePaths(ResultSettings resultSettings, MetadataSettings metadataSettings, ICompareSettings compareSettings, string personDisplayDirectory)
{
List<FilePath> 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<string?> changes = [];
string[] personKeyDirectories;
PersonContainer personContainer;
List<PersonContainer> results = [];
string? personDisplayDirectoryName;
List<PersonContainer> personContainers;
List<FilePath> personDisplayDirectoryAllFilePaths;
char[] personCharacters = peopleSettings.PersonCharacters.ToArray();
List<PersonKeyFormattedAndPersonBirthday> 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<string?> changes;
List<string?> allChanges = [];
string innerGroupDirectoryName;
PersonDirectory personDirectory;
List<PersonContainer> collection;
const char exclamationPoint = '!';
string[] personDisplayDirectories;
List<PersonContainer> 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<PersonContainer> GetPersonContainersGroups(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, ICompareSettings compareSettings, string[] groupDirectories)
{
List<PersonContainer> results;
List<string?> changes;
string groupDirectoryName;
List<string?> allChanges = [];
List<PersonContainer> collection;
List<PersonContainer> 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<PersonContainer> GetPersonContainers(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, ICompareSettings compareSettings)
{
List<PersonContainer> 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<long> GetPersonKeys(ReadOnlyCollection<PersonContainer> personContainers)
{
List<long> 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<PersonKeyFormattedAndPersonBirthday> 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<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> GetDirectoryAndTicksCollection(PeopleSettings peopleSettings, string? rootDirectory)
{
List<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> 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<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> GetGenealogicalDataCommunicationDirectories(PeopleSettings peopleSettings)
{
ReadOnlyCollection<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> results;
string? genealogicalDataCommunicationDirectory = Path.GetDirectoryName(peopleSettings.GenealogicalDataCommunicationFile);
results = GetDirectoryAndTicksCollection(peopleSettings, genealogicalDataCommunicationDirectory);
return results;
}
private static string? TryToFind(PeopleSettings peopleSettings, string a2PeopleSingletonDirectory, List<RecordB> 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<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> GetJLinkResolvedDirectories(PeopleSettings peopleSettings, List<string> resolvedDirectories)
{
List<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> 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<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> GetJLinkDirectories(ResultSettings resultSettings, PeopleSettings peopleSettings)
{
ReadOnlyCollection<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> results;
string[] files;
string? foundPath;
int totalFiles = 0;
string checkDirectory;
WindowsShortcut windowsShortcut;
List<string> 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<RecordB> 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<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> GetJLinkResolvedDirectories(ResultSettings resultSettings, PeopleSettings peopleSettings)
{
ReadOnlyCollection<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> 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;
}
}

View File

@ -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;
namespace View_by_Distance.People.Models.Stateless;
internal static class ReadOnlyCollectionsLogic
{
private static void SetPersonCollectionsAfterSetSkipCollections(PeopleSettings peopleSettings, ReadOnlyCollection<PersonContainer> personContainers, Dictionary<string, string> personKeyFormattedToNewestPersonKeyFormatted, List<string> 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<PersonContainer> personContainers, Dictionary<int, List<FilePathAndWholePercentages>> skipCollection, Dictionary<int, List<FilePathAndWholePercentages>> skipNotSkipCollection, ReadOnlyCollection<string> skipNotSkipDirectories)
{
string checkFile;
int? wholePercentages;
List<FilePath> distinct = [];
List<string> distinctFiles = [];
List<string> 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<PersonContainer> personContainers, ReadOnlyCollection<long> personKeys)
{
ReadOnlyCollections result;
List<string> personKeyFormattedCollection = [];
Dictionary<int, List<FilePathAndWholePercentages>> skipCollection = [];
Dictionary<string, string> personKeyFormattedToNewestPersonKeyFormatted = [];
Dictionary<int, List<FilePathAndWholePercentages>> skipNotSkipCollection = [];
ReadOnlyCollection<string> skipNotSkipDirectories = distanceSettings.SkipNotSkipDirectories.Where(l => !string.IsNullOrEmpty(l)).ToArray().AsReadOnly();
SetSkipCollections(resultSettings, compareSettings, personContainers, skipCollection, skipNotSkipCollection, skipNotSkipDirectories);
SetPersonCollectionsAfterSetSkipCollections(peopleSettings, personContainers, personKeyFormattedToNewestPersonKeyFormatted, personKeyFormattedCollection);
ReadOnlyCollection<PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName> jLinkResolvedDirectories = IPeople.GetJLinkResolvedDirectories(resultSettings, peopleSettings);
ReadOnlyCollection<long> jLinkResolvedPersonKeys = jLinkResolvedDirectories.Select(l => l.KeyTicks).ToArray().AsReadOnly();
result = new(jLinkResolvedPersonKeys,
personContainers,
personKeyFormattedCollection.AsReadOnly(),
personKeyFormattedToNewestPersonKeyFormatted.AsReadOnly(),
personKeys,
skipCollection.AsReadOnly(),
skipNotSkipCollection.AsReadOnly(),
skipNotSkipDirectories);
return result;
}
}

1
Rename/.vscode/read-me.md vendored Normal file
View File

@ -0,0 +1 @@
# Read Me

View File

@ -4,17 +4,20 @@
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<TargetFramework>net8.0</TargetFramework>
<UserSecretsId>fa0fa59b-afe4-4960-9afc-18fcbc7fb41b</UserSecretsId>
<TargetFramework>net9.0</TargetFramework>
<UserSecretsIdOld>fa0fa59b-afe4-4960-9afc-18fcbc7fb41b</UserSecretsIdOld>
<UserSecretsId>843db3e1-e18f-4cba-8b00-967529a32635</UserSecretsId>
</PropertyGroup>
<PropertyGroup>
<PackageId>Phares.View.by.Distance.Rename</PackageId>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<Version>8.0.101.1</Version>
<Authors>Mike Phares</Authors>
<Version>8.0.112.0</Version>
<Company>Phares</Company>
<Authors>Mike Phares</Authors>
<IncludeSymbols>true</IncludeSymbols>
<PackageReadmeFile>read-me.md</PackageReadmeFile>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
@ -34,12 +37,19 @@
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.4" />
<PackageReference Include="runtime.win-x64.Microsoft.DotNet.ILCompiler" Version="8.0.0-rc.2.23479.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<None Include=".vscode\read-me.md" Pack="true" PackagePath="\"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.7.1" />
<PackageReference Include="runtime.win-x64.Microsoft.DotNet.ILCompiler" Version="8.0.12" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="ShellProgressBar" Version="5.2.0" />
<PackageReference Include="System.Drawing.Common" Version="8.0.12" />
</ItemGroup>
<ItemGroup>
<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Metadata\AA.Metadata.csproj" />

View File

@ -1,11 +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(string Company,
int MaxDegreeOfParallelism,
bool RequireRootDirectoryExists)
public record AppSettings(ResultSettings ResultSettings,
MetadataSettings MetadataSettings,
RenameSettings RenameSettings)
{
public override string ToString()
@ -14,6 +16,42 @@ public record AppSettings(string Company,
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<ResultSettings>();
MetadataSettings? metadataSettings = configurationRoot.GetSection(nameof(MetadataSettings)).Get<MetadataSettings>();
RenameSettings? renameSettings = configurationRoot.GetSection(nameof(RenameSettings)).Get<RenameSettings>();
#pragma warning restore IL3050, IL2026
if (resultSettings is null || metadataSettings is null || renameSettings?.Company is null)
{
List<string> 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)]

View File

@ -1,2 +0,0 @@
[*.cs]
csharp_preserve_single_line_statements = true

View File

@ -1,72 +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 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)
{
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;
if (!physicalFileProvider.Root.Contains("UserSecrets"))
continue;
throw new NotSupportedException(physicalFileProvider.Root);
}
}
}
private static void Verify(AppSettings _)
{
}
private static Models.AppSettings Get(AppSettings? appSettings)
{
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));
Verify(appSettings);
result = new(appSettings.Company,
appSettings.MaxDegreeOfParallelism.Value,
appSettings.RequireRootDirectoryExists.Value);
return result;
}
public static Models.AppSettings Get(IConfigurationRoot configurationRoot)
{
Models.AppSettings result;
#pragma warning disable IL3050, IL2026
AppSettings? appSettings = configurationRoot.Get<AppSettings>();
#pragma warning restore IL3050, IL2026
PreVerify(configurationRoot, appSettings);
result = Get(appSettings);
return result;
}
}
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(AppSettings))]
internal partial class BinderAppSettingsSourceGenerationContext : JsonSerializerContext
{
}

View File

@ -1,73 +0,0 @@
using Microsoft.Extensions.Configuration;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace View_by_Distance.Rename.Models.Binder;
public class RenameConfiguration
{
public string[]? IgnoreExtensions { get; set; }
public string[]? ValidImageFormatExtensions { get; set; }
public override string ToString()
{
string result = JsonSerializer.Serialize(this, BinderConfigurationSourceGenerationContext.Default.RenameConfiguration);
return result;
}
private static void PreVerify(IConfigurationRoot configurationRoot, RenameConfiguration? configuration)
{
if (configuration?.IgnoreExtensions is null)
{
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;
if (!physicalFileProvider.Root.Contains("UserSecrets"))
continue;
throw new NotSupportedException(physicalFileProvider.Root);
}
}
}
private static void Verify(RenameConfiguration configuration)
{
if (configuration.IgnoreExtensions is null || configuration.IgnoreExtensions.Length == 0) throw new NullReferenceException(nameof(configuration.IgnoreExtensions));
if (configuration.ValidImageFormatExtensions is null || configuration.ValidImageFormatExtensions.Length == 0) throw new NullReferenceException(nameof(configuration.ValidImageFormatExtensions));
}
private static Models.RenameConfiguration Get(RenameConfiguration? configuration, Shared.Models.MetadataConfiguration metadataConfiguration)
{
Models.RenameConfiguration result;
if (configuration is null) throw new NullReferenceException(nameof(configuration));
if (configuration.IgnoreExtensions is null) throw new NullReferenceException(nameof(configuration.IgnoreExtensions));
if (configuration.ValidImageFormatExtensions is null) throw new NullReferenceException(nameof(configuration.ValidImageFormatExtensions));
Verify(configuration);
result = new(metadataConfiguration,
configuration.IgnoreExtensions,
configuration.ValidImageFormatExtensions);
return result;
}
public static Models.RenameConfiguration Get(IConfigurationRoot configurationRoot, Shared.Models.MetadataConfiguration metadataConfiguration)
{
Models.RenameConfiguration result;
IConfigurationSection configurationSection = configurationRoot.GetSection(nameof(RenameConfiguration));
#pragma warning disable IL3050, IL2026
RenameConfiguration? configuration = configurationSection.Get<RenameConfiguration>();
#pragma warning restore IL3050, IL2026
PreVerify(configurationRoot, configuration);
result = Get(configuration, metadataConfiguration);
return result;
}
}
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(RenameConfiguration))]
internal partial class BinderConfigurationSourceGenerationContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,32 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace View_by_Distance.Rename.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
{
}

View File

@ -1,24 +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[] IgnoreExtensions,
string[] ValidImageFormatExtensions)
{
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
{
}

View File

@ -0,0 +1,38 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace View_by_Distance.Rename.Models;
public record RenameSettings(string Company,
string DefaultMaker,
string? FirstPassFile,
bool ForceNewId,
string[] IgnoreExtensions,
bool InPlace,
bool InPlaceMoveDirectory,
bool InPlaceWithOriginalName,
int MaxDegreeOfParallelism,
int MaxMilliSecondsPerCall,
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
{
}

View File

@ -15,11 +15,7 @@ public class Program
.AddEnvironmentVariables()
.AddUserSecrets<Program>();
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);
@ -28,17 +24,17 @@ public class Program
if (args is null)
throw new Exception("args is null!");
Shared.Models.Console console = new();
_ = new Rename(args, logger, configurationRoot, appSettings, silentIndex > -1, console);
_ = new Rename(args, logger, appSettings, silentIndex > -1, console);
}
catch (Exception ex)
{
logger?.LogError(ex, "Error!");
}
if (silentIndex > -1)
logger?.LogDebug("Done. Bye");
logger?.LogInformation("Done. Bye");
else
{
logger?.LogDebug("Done. Press 'Enter' to end");
logger?.LogInformation("Done. Press 'Enter' to end");
_ = Console.ReadLine();
}
}

View File

@ -1,29 +1,41 @@
using CliWrap;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using ShellProgressBar;
using System.Collections.ObjectModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Text.Json;
using View_by_Distance.Metadata.Models;
using View_by_Distance.Metadata.Models.Stateless.Methods;
using View_by_Distance.Metadata.Models.Stateless;
using View_by_Distance.Rename.Models;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Stateless.Methods;
using View_by_Distance.Shared.Models.Properties;
using View_by_Distance.Shared.Models.Stateless;
namespace View_by_Distance.Rename;
public class Rename : IRename
public partial class Rename : IRename, IDisposable
{
private record ToDo(string? Directory, FileHolder FileHolder, string File, bool JsonFile);
private record Record(DateTime DateTime, ExifDirectory ExifDirectory, string File, string JsonFile);
private sealed record ToDo(string? Directory,
FileInfo FileInfo,
string File,
bool JsonFile);
private readonly AppSettings _AppSettings;
private readonly RenameConfiguration _RenameConfiguration;
private sealed record Record(DateTime DateTime,
ExifDirectory ExifDirectory,
bool FastForwardMovingPictureExpertsGroupUsed,
FileHolder[] SidecarFiles,
bool HasDateTimeOriginal,
bool HasIgnoreKeyword,
string JsonFile);
public Rename(List<string> args, ILogger<Program>? logger, IConfigurationRoot configurationRoot, AppSettings appSettings, bool isSilent, IConsole console)
private ProgressBar? _ProgressBar;
private readonly ProgressBarOptions _ProgressBarOptions;
public Rename(List<string> args, ILogger<Program>? logger, AppSettings appSettings, bool isSilent, IConsole console)
{
if (isSilent)
{ }
@ -31,55 +43,58 @@ public class Rename : IRename
throw new NullReferenceException(nameof(args));
if (console is null)
throw new NullReferenceException(nameof(console));
_AppSettings = appSettings;
IRename rename = this;
long ticks = DateTime.Now.Ticks;
ResultConfiguration resultConfiguration = Metadata.Models.Binder.ResultConfiguration.Get(configurationRoot, appSettings.RequireRootDirectoryExists);
MetadataConfiguration metadataConfiguration = Metadata.Models.Binder.MetadataConfiguration.Get(configurationRoot, resultConfiguration);
RenameConfiguration renameConfiguration = Models.Binder.RenameConfiguration.Get(configurationRoot, metadataConfiguration);
_RenameConfiguration = renameConfiguration;
DirectoryInfo directoryInfo = new(Path.GetFullPath(resultConfiguration.RootDirectory));
logger?.LogInformation("{RootDirectory}", directoryInfo.FullName);
ReadOnlyCollection<Record> exifDirectories = GetExifDirectoryCollection(directoryInfo);
ReadOnlyCollection<ToDo> toDoCollection = GetToDoCollection(logger, ticks, exifDirectories);
ReadOnlyCollection<string> lines = RenameFilesInDirectories(toDoCollection);
if (lines.Count != 0)
{
File.WriteAllLines($"D:/Tmp/Phares/{DateTime.Now.Ticks}.tsv", lines);
_ = IPath.DeleteEmptyDirectories(directoryInfo.FullName);
}
_ProgressBarOptions = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
RenameWork(logger, appSettings, rename, ticks);
}
(ReadOnlyCollection<string>, FilePath?) IRename.ConvertAndGetFfmpegFiles(FilePath filePath)
void IRename.Tick() =>
_ProgressBar?.Tick();
void IRename.ConstructProgressBar(int maxTicks, string message)
{
_ProgressBar?.Dispose();
_ProgressBar = new(maxTicks, message, _ProgressBarOptions);
}
void IDisposable.Dispose()
{
_ProgressBar?.Dispose();
GC.SuppressFinalize(this);
}
ReadOnlyCollection<string> IRename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(IRenameSettings renameSettings, FilePath filePath)
{
List<string> results = [];
FilePath? result;
bool isIgnoreExtension;
bool isValidImageFormatExtension = _RenameConfiguration.ValidImageFormatExtensions.Contains(filePath.ExtensionLowered);
isIgnoreExtension = isValidImageFormatExtension && _RenameConfiguration.IgnoreExtensions.Contains(filePath.ExtensionLowered);
if (!isIgnoreExtension && isValidImageFormatExtension)
result = null;
else
bool isValidVideoFormatExtensions = renameSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered);
if (isValidVideoFormatExtensions)
{
CommandTask<CommandResult> commandTask = Cli.Wrap("ffmpeg.exe")
// .WithArguments(new[] { "-ss", "00:00:00", "-t", "00:00:00", "-i", files[i], "-qScale:v", "2", "-r", "0.01", $"{fileHolder.Name}-%4d.jpg" })
.WithArguments(new[] { "-i", filePath.FullName, "-vFrames", "1", $"{filePath.Name}-%4d.jpg" })
.WithWorkingDirectory(filePath.DirectoryName)
.ExecuteAsync();
commandTask.Task.Wait();
results.AddRange(Directory.GetFiles(filePath.DirectoryName, $"{filePath.Name}-*.jpg", SearchOption.TopDirectoryOnly));
if (results.Count == 0)
throw new Exception();
result = IId.GetFilePath(_RenameConfiguration.MetadataConfiguration, results[0]);
if (!result.Name.EndsWith("-0001.jpg"))
throw new Exception();
isValidImageFormatExtension = _RenameConfiguration.ValidImageFormatExtensions.Contains(result.ExtensionLowered);
isIgnoreExtension = isValidImageFormatExtension && _RenameConfiguration.IgnoreExtensions.Contains(result.ExtensionLowered);
if (isIgnoreExtension || !isValidImageFormatExtension)
throw new Exception();
if (result.DirectoryName is null)
throw new NullReferenceException(nameof(result.DirectoryName));
bool check;
try
{
CommandTask<CommandResult> commandTask = Cli.Wrap("L:/Git/ffmpeg-2024-10-02-git-358fdf3083-full_build/bin/ffmpeg.exe")
.WithArguments(["-i", filePath.FullName, "-vf", "select=eq(n\\,0)", "-q:v", "1", $"{filePath.Name}-%4d.jpg"])
.WithWorkingDirectory(filePath.DirectoryFullPath)
.ExecuteAsync();
commandTask.Task.Wait();
check = true;
}
catch (Exception)
{
check = false;
}
if (check)
{
results.AddRange(Directory.GetFiles(filePath.DirectoryFullPath, $"{filePath.Name}-*.jpg", SearchOption.TopDirectoryOnly));
if (results.Count == 0)
throw new Exception();
File.SetCreationTime(results[0], new(filePath.CreationTicks));
File.SetLastWriteTime(results[0], new(filePath.LastWriteTicks));
Thread.Sleep(100);
}
}
return new(new(results), result);
return results.AsReadOnly();
}
#pragma warning disable CA1416
@ -117,93 +132,334 @@ public class Rename : IRename
#pragma warning restore CA1416
private void GetExifDirectoryCollection(IRename rename, List<(string, FileInfo, ExifDirectory)> exifDirectories, IEnumerable<string> files, A_Metadata metadata)
private void NonParallelismAndInPlace(AppSettings appSettings, IRename rename, ReadOnlyCollection<int> ids, ExifDirectory exifDirectory, MinimumYearAndPathCombined minimumYearAndPathCombined, bool fastForwardMovingPictureExpertsGroupUsed, FileHolder[] sidecarFiles)
{
FileInfo fileInfo;
if (exifDirectory.FilePath.Id is null)
throw new NotImplementedException();
int i = 0;
ToDo toDo;
const string jpg = ".jpg";
const string jpeg = ".jpeg";
List<ToDo> toDoCollection = [];
DateTime? dateTime = IDate.GetDateTimeOriginal(exifDirectory);
ReadOnlyCollection<string> keywords = IMetadata.GetKeywords(exifDirectory);
bool hasIgnoreKeyword = appSettings.MetadataSettings.IgnoreRulesKeyWords.Any(keywords.Contains);
string checkFileExtension = exifDirectory.FilePath.ExtensionLowered == jpeg ? jpg : exifDirectory.FilePath.ExtensionLowered;
bool hasDateTimeOriginal = dateTime is not null;
string paddedId = IId.GetPaddedId(appSettings.ResultSettings, appSettings.MetadataSettings, exifDirectory.FilePath.Id.Value, hasIgnoreKeyword, hasDateTimeOriginal, i);
string checkDirectory = appSettings.RenameSettings.InPlaceWithOriginalName ? Path.Combine(exifDirectory.FilePath.DirectoryFullPath, exifDirectory.FilePath.FileNameFirstSegment) : exifDirectory.FilePath.DirectoryFullPath;
string checkFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}");
if (checkFile != exifDirectory.FilePath.FullName)
{
if (File.Exists(checkFile))
{
checkFile = string.Concat(checkFile, ".del");
if (File.Exists(checkFile))
throw new NotImplementedException();
}
toDo = new(Directory: checkDirectory,
FileInfo: new(exifDirectory.FilePath.FullName),
File: checkFile,
JsonFile: false);
toDoCollection.Add(toDo);
if (sidecarFiles.Length != 0)
{
if (appSettings.RenameSettings.InPlace)
throw new NotSupportedException($"Must use {nameof(appSettings.RenameSettings.InPlaceWithOriginalName)} when sidecar file(s) are present!");
dateTime ??= IDate.GetMinimum(exifDirectory);
Record record = new(DateTime: dateTime.Value,
ExifDirectory: exifDirectory,
FastForwardMovingPictureExpertsGroupUsed: fastForwardMovingPictureExpertsGroupUsed,
SidecarFiles: sidecarFiles,
HasDateTimeOriginal: hasDateTimeOriginal,
HasIgnoreKeyword: hasIgnoreKeyword,
JsonFile: minimumYearAndPathCombined.PathCombined);
toDoCollection.AddRange(GetSidecarFiles(appSettings, record, [], checkDirectory, paddedId));
}
_ = RenameFilesInDirectories(appSettings.RenameSettings, rename, new(toDoCollection));
string jsonFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}.json");
File.Move(minimumYearAndPathCombined.PathCombined, jsonFile, overwrite: true);
if (appSettings.RenameSettings.InPlaceWithOriginalName && ids.Count > 0)
{
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);
}
}
}
private List<FirstPass> GetFirstPassCollection(ILogger<Program>? logger, AppSettings appSettings, IRename rename, long ticks, ReadOnlyCollection<int> ids, IEnumerable<string> files, A_Metadata metadata)
{
List<FirstPass> results = [];
int index = -1;
FilePath filePath;
FilePath? ffmpegFilePath;
TimeSpan timeSpan;
FirstPass firstPass;
string directoryName;
ExifDirectory exifDirectory;
ReadOnlyCollection<string> ffmpegFiles;
List<FileHolder> sidecarFiles;
DeterministicHashCode deterministicHashCode;
foreach (string file in files)
bool fastForwardMovingPictureExpertsGroupUsed;
MinimumYearAndPathCombined minimumYearAndPathCombined;
FilePath? fastForwardMovingPictureExpertsGroupFilePath;
ReadOnlyCollection<string>? fastForwardMovingPictureExpertsGroupFiles;
ReadOnlyDictionary<string, List<FileHolder>> keyValuePairs = IMetadata.GetKeyValuePairs(files);
foreach (KeyValuePair<string, List<FileHolder>> keyValuePair in keyValuePairs)
{
filePath = IId.GetFilePath(_RenameConfiguration.MetadataConfiguration, file);
if (filePath.ExtensionLowered is ".paddedId" or ".lsv")
continue;
if (files.Contains($"{filePath.FullName}.paddedId"))
continue;
if (filePath.Id is not null && (filePath.IsIdFormat || filePath.IsPaddedIdFormat))
continue;
(ffmpegFiles, ffmpegFilePath) = rename.ConvertAndGetFfmpegFiles(filePath);
if (ffmpegFilePath is not null)
filePath = ffmpegFilePath;
if (filePath.Id is not null)
deterministicHashCode = new(null, filePath.Id, null);
else
deterministicHashCode = rename.GetDeterministicHashCode(filePath);
(fileInfo, exifDirectory) = metadata.GetMetadataCollection(_RenameConfiguration.MetadataConfiguration, filePath, deterministicHashCode);
exifDirectories.Add(new(file, fileInfo, exifDirectory));
foreach (string ffmpegFile in ffmpegFiles)
File.Delete(ffmpegFile);
index += 1;
rename.Tick();
if (keyValuePair.Value.Count > 1 && !appSettings.RenameSettings.ForceNewId)
{
if (appSettings.RenameSettings.InPlaceMoveDirectory)
continue;
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 (appSettings.RenameSettings.SidecarExtensions.Contains(fileHolder.ExtensionLowered))
continue;
if (appSettings.RenameSettings.IgnoreExtensions.Contains(fileHolder.ExtensionLowered))
continue;
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 (!appSettings.RenameSettings.ForceNewId && filePath.Id is not null)
{
fastForwardMovingPictureExpertsGroupFiles = null;
deterministicHashCode = new(null, filePath.Id, null);
directoryName = Path.GetFileName(filePath.DirectoryFullPath);
if (appSettings.RenameSettings.InPlaceWithOriginalName || (appSettings.RenameSettings.InPlace && directoryName.EndsWith(filePath.Id.Value.ToString())))
continue;
}
else
{
fastForwardMovingPictureExpertsGroupFiles = rename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(appSettings.RenameSettings, filePath);
fastForwardMovingPictureExpertsGroupFilePath = fastForwardMovingPictureExpertsGroupFiles.Count == 0 ? null : FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, FileHolder.Get(fastForwardMovingPictureExpertsGroupFiles[0]), index);
deterministicHashCode = fastForwardMovingPictureExpertsGroupFilePath is null ? rename.GetDeterministicHashCode(filePath) : rename.GetDeterministicHashCode(fastForwardMovingPictureExpertsGroupFilePath);
}
sidecarFiles = [];
filePath = FilePath.Get(filePath, deterministicHashCode);
for (int i = 0; i < keyValuePair.Value.Count; i++)
{
if (keyValuePair.Value[i].ExtensionLowered == fileHolder.ExtensionLowered)
continue;
sidecarFiles.Add(keyValuePair.Value[i]);
}
try
{ (minimumYearAndPathCombined, exifDirectory) = metadata.GetMetadataCollection(appSettings.ResultSettings, appSettings.MetadataSettings, filePath); }
catch (Exception)
{
logger?.LogWarning("<{filePath}>", filePath.FullName);
continue;
}
fastForwardMovingPictureExpertsGroupUsed = fastForwardMovingPictureExpertsGroupFiles is not null && fastForwardMovingPictureExpertsGroupFiles.Count > 0;
if (fastForwardMovingPictureExpertsGroupUsed && fastForwardMovingPictureExpertsGroupFiles is not null)
{
foreach (string fastForwardMovingPictureExpertsGroupFile in fastForwardMovingPictureExpertsGroupFiles)
File.Delete(fastForwardMovingPictureExpertsGroupFile);
}
if (appSettings.RenameSettings.InPlace || appSettings.RenameSettings.InPlaceWithOriginalName)
NonParallelismAndInPlace(appSettings, rename, ids, exifDirectory, minimumYearAndPathCombined, fastForwardMovingPictureExpertsGroupUsed, sidecarFiles.ToArray());
if (!fastForwardMovingPictureExpertsGroupUsed && appSettings.RenameSettings.InPlaceMoveDirectory && appSettings.RenameSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered))
fastForwardMovingPictureExpertsGroupUsed = true;
firstPass = new(exifDirectory, fastForwardMovingPictureExpertsGroupUsed, minimumYearAndPathCombined, sidecarFiles.ToArray());
results.Add(firstPass);
}
timeSpan = new(DateTime.Now.Ticks - ticks);
if (timeSpan.TotalMilliseconds > appSettings.RenameSettings.MaxMilliSecondsPerCall)
break;
}
}
private static ReadOnlyCollection<Record> GetExifDirectoryCollection(List<(string, FileInfo, ExifDirectory)> exifDirectories)
{
List<Record> results = [];
DateTime? dateTime;
foreach ((string file, FileInfo fileInfo, ExifDirectory exifDirectory) in exifDirectories)
{
dateTime = IDate.GetDateTimeOriginal(exifDirectory);
dateTime ??= IDate.GetMinimum(exifDirectory);
results.Add(new(dateTime.Value, exifDirectory, file, fileInfo.FullName));
}
return new(results);
}
private ReadOnlyCollection<Record> GetExifDirectoryCollection(DirectoryInfo directoryInfo)
{
ReadOnlyCollection<Record> results;
IRename rename = this;
List<(string, FileInfo, ExifDirectory)> exifDirectories = [];
int appSettingsMaxDegreeOfParallelism = _AppSettings.MaxDegreeOfParallelism;
IEnumerable<string> files = Directory.EnumerateFiles(directoryInfo.FullName, "*", SearchOption.AllDirectories);
A_Metadata metadata = new(_RenameConfiguration.MetadataConfiguration);
if (appSettingsMaxDegreeOfParallelism == 1)
GetExifDirectoryCollection(rename, exifDirectories, files, metadata);
else
{
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = appSettingsMaxDegreeOfParallelism };
ProgressBar progressBar = new(123000, "EnumerateFiles load", new ProgressBarOptions() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true });
files.AsParallel().ForAll(A_Metadata.SetExifDirectoryCollection(rename, _RenameConfiguration.MetadataConfiguration, metadata, exifDirectories, () => progressBar.Tick()));
if (progressBar.CurrentTick != exifDirectories.Count)
throw new NotSupportedException();
}
results = GetExifDirectoryCollection(exifDirectories);
return results;
}
private static void VerifyIntMinValueLength(ReadOnlyCollection<Record> exifDirectories, int intMinValueLength)
private static ReadOnlyCollection<Record> GetRecordCollection(AppSettings appSettings, List<FirstPass> collection)
{
foreach ((DateTime _, ExifDirectory exifDirectory, string _, string _) in exifDirectories)
List<Record> results = [];
Record record;
DateTime? dateTime;
bool hasIgnoreKeyword;
bool hasDateTimeOriginal;
ReadOnlyCollection<string> keywords;
foreach (FirstPass firstPass in collection)
{
if (exifDirectory.Id is null)
dateTime = IDate.GetDateTimeOriginal(firstPass.ExifDirectory);
hasDateTimeOriginal = dateTime is not null;
dateTime ??= IDate.GetMinimum(firstPass.ExifDirectory);
keywords = IMetadata.GetKeywords(firstPass.ExifDirectory);
hasIgnoreKeyword = appSettings.MetadataSettings.IgnoreRulesKeyWords.Any(l => keywords.Contains(l));
record = new(DateTime: dateTime.Value,
ExifDirectory: firstPass.ExifDirectory,
FastForwardMovingPictureExpertsGroupUsed: firstPass.FastForwardMovingPictureExpertsGroupUsed,
SidecarFiles: firstPass.SidecarFiles,
HasDateTimeOriginal: hasDateTimeOriginal,
HasIgnoreKeyword: hasIgnoreKeyword,
JsonFile: firstPass.MinimumYearAndPathCombined.PathCombined);
results.Add(record);
}
return results.AsReadOnly();
}
private ReadOnlyCollection<Record> GetRecordCollection(ILogger<Program>? logger, AppSettings appSettings, IRename rename, long ticks, ReadOnlyCollection<int> ids, string sourceDirectory, ReadOnlyCollection<string> files)
{
ReadOnlyCollection<Record> results;
FirstPass firstPass;
List<FirstPass> collection;
string? checkFile = string.IsNullOrEmpty(appSettings.RenameSettings.FirstPassFile) ? null : Path.Combine(sourceDirectory, appSettings.RenameSettings.FirstPassFile);
if (!string.IsNullOrEmpty(checkFile) && File.Exists(checkFile))
{
string json = File.ReadAllText(checkFile);
collection = JsonSerializer.Deserialize(json, FirstPassCollectionSourceGenerationContext.Default.ListFirstPass) ?? throw new Exception();
}
else
{
collection = [];
A_Metadata metadata = new(appSettings.ResultSettings, appSettings.MetadataSettings);
int appSettingsMaxDegreeOfParallelism = appSettings.RenameSettings.MaxDegreeOfParallelism;
int filesCount = appSettingsMaxDegreeOfParallelism == 1 ? files.Count : 123000;
rename.ConstructProgressBar(filesCount, "EnumerateFiles load");
if (appSettingsMaxDegreeOfParallelism == 1)
collection.AddRange(GetFirstPassCollection(logger, appSettings, rename, ticks, ids, files, metadata));
else
{
List<string> distinct = [];
List<MetadataGroup> metadataGroups = [];
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = appSettingsMaxDegreeOfParallelism };
files.AsParallel().ForAll(IMetadata.SetExifDirectoryCollection(rename, appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.RenameSettings, metadata, distinct, metadataGroups));
Thread.Sleep(500);
foreach (MetadataGroup metadataGroup in metadataGroups)
{
if (metadataGroup.FastForwardMovingPictureExpertsGroupUsed || !appSettings.RenameSettings.InPlaceMoveDirectory || !appSettings.RenameSettings.ValidVideoFormatExtensions.Contains(metadataGroup.FilePath.ExtensionLowered))
firstPass = new(metadataGroup.ExifDirectory, metadataGroup.FastForwardMovingPictureExpertsGroupUsed, metadataGroup.MinimumYearAndPathCombined, metadataGroup.SidecarFiles.ToArray());
else
firstPass = new(metadataGroup.ExifDirectory, FastForwardMovingPictureExpertsGroupUsed: true, metadataGroup.MinimumYearAndPathCombined, metadataGroup.SidecarFiles.ToArray());
collection.Add(firstPass);
}
}
if (!string.IsNullOrEmpty(checkFile))
{
string json = JsonSerializer.Serialize(collection, FirstPassCollectionSourceGenerationContext.Default.ListFirstPass);
File.WriteAllText(Path.Combine(sourceDirectory, $"{ticks}.json"), json);
}
}
results = GetRecordCollection(appSettings, collection);
return results;
}
private static void VerifyIntMinValueLength(MetadataSettings metadataSettings, ReadOnlyCollection<Record> recordCollection)
{
foreach (Record record in recordCollection)
{
if (record.ExifDirectory.FilePath.Id is null)
continue;
if (intMinValueLength < exifDirectory.Id.Value.ToString().Length)
if (metadataSettings.IntMinValueLength < record.ExifDirectory.FilePath.Id.Value.ToString().Length)
throw new NotSupportedException();
}
}
private ReadOnlyCollection<ToDo> GetToDoCollection(ILogger<Program>? logger, long ticks, ReadOnlyCollection<Record> exifDirectories)
private static string GetTFW(Record record, bool? isWrongYear) =>
string.Concat(record.HasDateTimeOriginal ? "T" : "F", isWrongYear is not null && isWrongYear.Value ? "W" : record.FastForwardMovingPictureExpertsGroupUsed ? "V" : "I");
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(AppSettings appSettings, DirectoryInfo directoryInfo, Record record, ReadOnlyCollection<int> ids, bool multipleDirectoriesWithFiles, string paddedId)
{
string? result;
string year = record.DateTime.Year.ToString();
string checkDirectoryName = Path.GetFileName(record.ExifDirectory.FilePath.DirectoryFullPath);
if (multipleDirectoriesWithFiles && !checkDirectoryName.Contains(year))
result = null;
else
{
(bool? isWrongYear, string[] years) = IDate.IsWrongYear(directoryInfo, record.ExifDirectory.FilePath, record.ExifDirectory);
if (appSettings.RenameSettings.InPlaceMoveDirectory && !record.ExifDirectory.FilePath.FileNameFirstSegment.Contains(paddedId))
result = null;
else
{
if (record.ExifDirectory.FilePath.FullName[..2] != directoryInfo.FullName[..2])
isWrongYear = null;
string tfw = GetTFW(record, isWrongYear);
string? maker = IMetadata.GetMaker(record.ExifDirectory);
string rootDirectory = appSettings.ResultSettings.RootDirectory;
string[] segments = checkDirectoryName.Split(years, StringSplitOptions.None);
(int seasonValue, string seasonName) = IDate.GetSeason(record.DateTime.DayOfYear);
string? splat = checkDirectoryName.Length > 3 && checkDirectoryName[^3..][1] == '!' ? checkDirectoryName[^3..] : null;
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));
}
}
return result;
}
private static List<ToDo> GetSidecarFiles(AppSettings appSettings, Record record, List<string> distinct, string checkDirectory, string paddedId)
{
List<ToDo> results = [];
int season;
string maker;
ToDo toDo;
string checkFile;
FilePath filePath;
string checkFileExtension;
foreach (FileHolder fileHolder in record.SidecarFiles)
{
checkFileExtension = fileHolder.ExtensionLowered;
filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, index: null);
checkFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}");
if (checkFile == filePath.FullName)
continue;
if (File.Exists(checkFile))
{
checkFile = string.Concat(checkFile, ".del");
if (File.Exists(checkFile))
continue;
}
if (distinct.Contains(checkFile))
continue;
distinct.Add(checkFile);
toDo = new(Directory: checkDirectory,
FileInfo: new(filePath.FullName),
File: checkFile,
JsonFile: false);
results.Add(toDo);
}
return results;
}
private static bool? GetDirectoryCheck(ResultSettings resultSettings)
{
bool? result = null;
IEnumerable<string> files;
string[] directories = Directory.GetDirectories(resultSettings.RootDirectory, "*", SearchOption.TopDirectoryOnly);
foreach (string directory in directories)
{
files = Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories);
foreach (string _ in files)
{
if (result is null)
result = false;
else if (result.Value)
result = true;
break;
}
if (result is not null && result.Value)
break;
}
return result;
}
private static ReadOnlyCollection<ToDo> GetToDoCollection(AppSettings appSettings, DirectoryInfo directoryInfo, ReadOnlyCollection<int> ids, ReadOnlyCollection<string> files, ReadOnlyCollection<Record> recordCollection)
{
List<ToDo> results = [];
ToDo? toDo;
Record record;
string jsonFile;
string paddedId;
string checkFile;
string seasonName;
FilePath filePath;
FileInfo[] matches;
string directoryName;
FileHolder fileHolder;
string? checkDirectory;
@ -212,25 +468,24 @@ public class Rename : IRename
List<string> distinct = [];
const string jpeg = ".jpeg";
string jsonFileSubDirectory;
int intMinValueLength = int.MinValue.ToString().Length;
VerifyIntMinValueLength(exifDirectories, intMinValueLength);
ReadOnlyCollection<Record> records = new((from l in exifDirectories orderby l.DateTime select l).ToArray());
for (int i = 0; i < records.Count; i++)
bool? directoryCheck = GetDirectoryCheck(appSettings.ResultSettings);
VerifyIntMinValueLength(appSettings.MetadataSettings, recordCollection);
bool multipleDirectoriesWithFiles = directoryCheck is not null && directoryCheck.Value;
ReadOnlyCollection<FileInfo> collection = (from l in files select new FileInfo(l)).ToArray().AsReadOnly();
ReadOnlyCollection<Record> sorted = (from l in recordCollection orderby l.DateTime select l).ToArray().AsReadOnly();
for (int i = 0; i < sorted.Count; i++)
{
record = records[i];
if (record.ExifDirectory.Id is null)
record = sorted[i];
if (record.ExifDirectory.FilePath.Id is null)
continue;
fileHolder = new(record.File);
if (fileHolder.DirectoryName is null)
paddedId = IId.GetPaddedId(appSettings.ResultSettings, appSettings.MetadataSettings, record.ExifDirectory.FilePath.Id.Value, record.HasIgnoreKeyword, record.HasDateTimeOriginal, i);
checkDirectory = GetCheckDirectory(appSettings, directoryInfo, record, ids, multipleDirectoriesWithFiles, paddedId);
if (string.IsNullOrEmpty(checkDirectory))
continue;
maker = IMetadata.GetMaker(record.ExifDirectory.ExifDirectoryBase);
(season, seasonName) = IDate.GetSeason(record.DateTime.DayOfYear);
checkFileExtension = fileHolder.ExtensionLowered == jpeg ? jpg : fileHolder.ExtensionLowered;
checkDirectory = Path.Combine(fileHolder.DirectoryName, $"{record.DateTime.Year}.{season} {seasonName}{maker}");
checkFileExtension = record.ExifDirectory.FilePath.ExtensionLowered == jpeg ? jpg : record.ExifDirectory.FilePath.ExtensionLowered;
jsonFileSubDirectory = Path.GetDirectoryName(Path.GetDirectoryName(record.JsonFile)) ?? throw new Exception();
paddedId = IId.GetPaddedId(intMinValueLength, _RenameConfiguration.MetadataConfiguration.Offset + i, record.ExifDirectory.Id.Value);
checkFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}");
if (checkFile == fileHolder.FullName)
if (checkFile == record.ExifDirectory.FilePath.FullName)
continue;
if (File.Exists(checkFile))
{
@ -238,16 +493,46 @@ public class Rename : IRename
if (File.Exists(checkFile))
continue;
}
(directoryName, _) = IPath.GetDirectoryNameAndIndex(_RenameConfiguration.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);
jsonFile = Path.Combine(jsonFileSubDirectory, directoryName, $"{record.ExifDirectory.FilePath.Id.Value}{checkFileExtension}.json");
if (record.JsonFile != jsonFile)
results.Add(new(null, new(record.JsonFile), jsonFile, JsonFile: true));
{
fileHolder = FileHolder.Get(record.JsonFile);
if (fileHolder.Exists)
{
filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, index: null);
toDo = new(Directory: null,
FileInfo: new(filePath.FullName),
File: jsonFile,
JsonFile: true);
results.Add(toDo);
}
}
if (distinct.Contains(checkFile))
continue;
toDo = null;
distinct.Add(checkFile);
results.Add(new(checkDirectory, fileHolder, checkFile, JsonFile: false));
if (files.Contains(record.ExifDirectory.FilePath.FullName))
toDo = new(Directory: checkDirectory,
FileInfo: new(record.ExifDirectory.FilePath.FullName),
File: checkFile,
JsonFile: false);
else
{
matches = (from l in collection where record.ExifDirectory.FilePath.Name == l.Name select l).ToArray();
toDo = matches.Length != 1 ? null : new(Directory: checkDirectory,
FileInfo: matches[0],
File: checkFile,
JsonFile: false);
}
if (toDo is null)
continue;
results.Add(toDo);
if (record.SidecarFiles.Length == 0)
continue;
results.AddRange(GetSidecarFiles(appSettings, record, distinct, checkDirectory, paddedId));
}
return new(results);
return results.AsReadOnly();
}
private static void VerifyDirectories(ReadOnlyCollection<ToDo> toDoCollection)
@ -263,17 +548,27 @@ public class Rename : IRename
}
}
private ReadOnlyCollection<string> RenameFilesInDirectories(ReadOnlyCollection<ToDo> toDoCollection)
private ReadOnlyCollection<string> RenameFilesInDirectories(RenameSettings renameSettings, IRename rename, ReadOnlyCollection<ToDo> toDoCollection)
{
List<string> results = [];
VerifyDirectories(toDoCollection);
bool useProgressBar = !renameSettings.InPlace && !renameSettings.InPlaceWithOriginalName;
if (useProgressBar)
rename.ConstructProgressBar(toDoCollection.Count, "Move Files");
foreach (ToDo toDo in toDoCollection)
{
if (useProgressBar)
_ProgressBar?.Tick();
if (!toDo.FileInfo.Exists)
continue;
if (toDo.JsonFile)
{
if (File.Exists(toDo.File))
File.Delete(toDo.File);
File.Move(toDo.FileHolder.FullName, toDo.File);
try
{ File.Move(toDo.FileInfo.FullName, toDo.File); }
catch (Exception)
{ continue; }
}
else if (toDo.Directory is null)
throw new NotSupportedException();
@ -281,11 +576,78 @@ public class Rename : IRename
{
if (File.Exists(toDo.File))
File.Delete(toDo.File);
File.Move(toDo.FileHolder.FullName, toDo.File);
results.Add($"{toDo.FileHolder.FullName}\t{toDo.File}");
try
{ File.Move(toDo.FileInfo.FullName, toDo.File); }
catch (Exception)
{ continue; }
results.Add($"{toDo.FileInfo.FullName}\t{toDo.File}");
}
}
if (useProgressBar)
_ProgressBar?.Dispose();
return results.AsReadOnly();
}
private static void SaveIdentifiersToDisk(long ticks, AppSettings appSettings, ReadOnlyCollection<Record> recordCollection)
{
string paddedId;
Identifier identifier;
List<Identifier> identifiers = [];
string aMetadataCollectionDirectory = IResult.GetResultsDateGroupDirectory(appSettings.ResultSettings, nameof(A_Metadata), appSettings.ResultSettings.ResultCollection);
foreach (Record record in recordCollection)
{
if (record.ExifDirectory.FilePath.Id is null)
continue;
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.ExifDirectory.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<int> GetIds(RenameSettings renameSettings)
{
ReadOnlyCollection<int> results;
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(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<Program>? logger, AppSettings appSettings, IRename rename, long ticks)
{
ReadOnlyCollection<int> ids = GetIds(appSettings.RenameSettings);
string sourceDirectory = Path.GetFullPath(appSettings.ResultSettings.RootDirectory);
if (!Directory.Exists(sourceDirectory))
_ = Directory.CreateDirectory(sourceDirectory);
logger?.LogInformation("{Ticks} {RootDirectory}", ticks, sourceDirectory);
ReadOnlyCollection<string> files = Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories).ToArray().AsReadOnly();
if (files.Count > 0)
_ = IPath.DeleteEmptyDirectories(appSettings.ResultSettings.RootDirectory);
ReadOnlyCollection<Record> recordCollection = GetRecordCollection(logger, appSettings, rename, ticks, ids, sourceDirectory, files);
SaveIdentifiersToDisk(ticks, appSettings, recordCollection);
if (appSettings.RenameSettings.InPlace || appSettings.RenameSettings.InPlaceWithOriginalName)
{
if (recordCollection.Count > 0)
recordCollection = new([]);
string aMetadataSingletonDirectory = IResult.GetResultsGroupDirectory(appSettings.ResultSettings, nameof(A_Metadata));
_ = IPath.DeleteEmptyDirectories(aMetadataSingletonDirectory);
}
if (!appSettings.RenameSettings.OnlySaveIdentifiersToDisk)
{
DirectoryInfo directoryInfo = new(sourceDirectory);
ReadOnlyCollection<ToDo> toDoCollection = GetToDoCollection(appSettings, directoryInfo, ids, files, recordCollection);
ReadOnlyCollection<string> lines = RenameFilesInDirectories(appSettings.RenameSettings, rename, toDoCollection);
if (lines.Count != 0)
{
File.WriteAllLines($"D:/Tmp/Phares/{DateTime.Now.Ticks}.tsv", lines);
_ = IPath.DeleteEmptyDirectories(directoryInfo.FullName);
}
}
return new(results);
}
}

1
Shared/.vscode/read-me.md vendored Normal file
View File

@ -0,0 +1 @@
# Read Me

View File

@ -1,38 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<PackageId>Phares.AA.Shared</PackageId>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<Version>8.0.101.1</Version>
<Authors>Mike Phares</Authors>
<Company>Phares</Company>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>Windows</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)'=='true'">
<DefineConstants>OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>Linux</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
<PackageReference Include="System.Text.Json" Version="7.0.3" />
</ItemGroup>
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<PackageId>Phares.AA.Shared</PackageId>
<Version>8.1.112.0</Version>
<Company>Phares</Company>
<Authors>Mike Phares</Authors>
<IncludeSymbols>true</IncludeSymbols>
<PackageReadmeFile>read-me.md</PackageReadmeFile>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>Windows</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)'=='true'">
<DefineConstants>OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>Linux</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<None Include=".vscode\read-me.md" Pack="true" PackagePath="\"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
</Project>

View File

@ -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
{
}

View File

@ -3,20 +3,22 @@ using System.Text.Json.Serialization;
namespace View_by_Distance.Shared.Models;
public record ExifDirectory(AviDirectory AviDirectory,
ExifDirectoryBase ExifDirectoryBase,
FileMetadataDirectory FileMetadataDirectory,
GifHeaderDirectory GifHeaderDirectory,
GpsDirectory GpsDirectory,
public record ExifDirectory(AviDirectory[] AviDirectories,
object? Encoding,
ExifDirectoryBase[] ExifBaseDirectories,
FileMetadataDirectory[] FileMetadataDirectories,
FilePath FilePath,
GifHeaderDirectory[] GifHeaderDirectories,
GpsDirectory[] GpsDirectories,
int? Height,
int? Id,
JpegDirectory JpegDirectory,
string OriginalFileName,
PhotoshopDirectory PhotoshopDirectory,
PngDirectory PngDirectory,
QuickTimeMovieHeaderDirectory QuickTimeMovieHeaderDirectory,
QuickTimeTrackHeaderDirectory QuickTimeTrackHeaderDirectory,
WebPDirectory WebPDirectory,
JpegDirectory[] JpegDirectories,
MakernoteDirectory[] MakernoteDirectories,
PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName,
PhotoshopDirectory[] PhotoshopDirectories,
PngDirectory[] PngDirectories,
QuickTimeMovieHeaderDirectory[] QuickTimeMovieHeaderDirectories,
QuickTimeTrackHeaderDirectory[] QuickTimeTrackHeaderDirectories,
WebPDirectory[] WebPDirectories,
int? Width)
{
@ -26,9 +28,28 @@ public record ExifDirectory(AviDirectory AviDirectory,
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)]
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(ExifDirectory))]
public partial class ExifDirectorySourceGenerationContext : JsonSerializerContext
{

View File

@ -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
{
}

View File

@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
namespace View_by_Distance.Shared.Models;
public record FaceEncoding(double[] RawEncoding, int Size);
[JsonSourceGenerationOptions(WriteIndented = false)]
[JsonSerializable(typeof(FaceEncoding))]
public partial class FaceEncodingGenerationContext : JsonSerializerContext
{
}

33
Shared/Models/FaceFile.cs Normal file
View File

@ -0,0 +1,33 @@
using System.Text.Json.Serialization;
namespace View_by_Distance.Shared.Models;
public record FaceFile(int? AreaPermyriad,
int? ConfidencePercent,
string? DMS,
DateTime DateTime,
FaceEncoding? FaceEncoding,
Dictionary<Stateless.FacePart, FacePoint[]>? FaceParts,
Location? Location,
string? Maker,
MappingFromPerson? MappingFromPerson,
string? Model,
OutputResolution? OutputResolution);
[JsonSourceGenerationOptions(WriteIndented = false)]
[JsonSerializable(typeof(FaceFile))]
public partial class FaceFileGenerationContext : JsonSerializerContext
{
}
[JsonSourceGenerationOptions(WriteIndented = false, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(FaceFile[]))]
public partial class FaceFileCollectionGenerationContext : JsonSerializerContext
{
}
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(FaceFile[]))]
public partial class FaceFileCollectionWriteIndentedGenerationContext : JsonSerializerContext
{
}

View File

@ -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
{
}

View File

@ -0,0 +1,39 @@
using System.Drawing;
using System.Text.Json.Serialization;
namespace View_by_Distance.Shared.Models;
[method: JsonConstructor]
public class FacePoint(int index, int x, int y) : Properties.IFacePoint
{
public int Index { get; } = index;
public int X { get; } = x;
public int Y { get; } = y;
private readonly Point _Point = new(x, y);
public override bool Equals(object? obj) => obj is FacePoint point && Equals(point);
#pragma warning disable IDE0070
public override int GetHashCode()
#pragma warning restore IDE0070
{
int hashCode = 1861411795;
hashCode = (hashCode * -1521134295) + _Point.GetHashCode();
hashCode = (hashCode * -1521134295) + Index.GetHashCode();
return hashCode;
}
public bool Equals(FacePoint? facePoint)
{
return facePoint is not null
&& X == facePoint.X
&& Y == facePoint.Y
&& Index == facePoint.Index;
}
public static bool operator ==(FacePoint point1, FacePoint point2) => point1.Equals(point2);
public static bool operator !=(FacePoint point1, FacePoint point2) => !(point1 == point2);
}

View File

@ -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<FaceEncoding?> FaceEncodings, ReadOnlyCollection<ReadOnlyCollection<FacePartAndFacePointArray>> 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
{
}

View File

@ -3,76 +3,113 @@ using System.Text.Json.Serialization;
namespace View_by_Distance.Shared.Models;
public class FileHolder
public record FileHolder(DateTime? CreationTime,
string? DirectoryFullPath,
bool Exists,
string ExtensionLowered,
string FullName,
int? Id,
DateTime? LastWriteTime,
long? Length,
string Name,
string NameWithoutExtension)
{
protected readonly DateTime? _CreationTime;
protected readonly string? _DirectoryName;
protected readonly bool _Exists;
protected readonly string _ExtensionLowered;
protected readonly string _FullName;
protected readonly int? _Id;
protected readonly DateTime? _LastWriteTime;
protected readonly long? _Length;
protected readonly string _Name;
protected readonly string _NameWithoutExtension;
public DateTime? CreationTime => _CreationTime;
public string? DirectoryName => _DirectoryName;
public bool Exists => _Exists;
public string ExtensionLowered => _ExtensionLowered;
public string FullName => _FullName;
public int? Id => _Id;
public DateTime? LastWriteTime => _LastWriteTime;
public long? Length => _Length;
public string Name => _Name;
public string NameWithoutExtension => _NameWithoutExtension;
public FileHolder(DateTime? creationTime, string? directoryName, bool exists, string extensionLowered, string fullName, int? id, DateTime? lastWriteTime, long? length, string name, string nameWithoutExtension)
{
_CreationTime = creationTime;
_DirectoryName = directoryName;
_Exists = exists;
_ExtensionLowered = extensionLowered;
_FullName = fullName;
_Id = id;
_LastWriteTime = lastWriteTime;
_Length = length;
_Name = name;
_NameWithoutExtension = nameWithoutExtension;
}
public FileHolder(FileInfo fileInfo, int? id)
{
if (fileInfo.Exists)
{
_CreationTime = fileInfo.CreationTime;
_CreationTime = fileInfo.CreationTime;
_LastWriteTime = fileInfo.LastWriteTime;
_Length = fileInfo.Length;
}
_DirectoryName = fileInfo.DirectoryName;
_Exists = fileInfo.Exists;
_ExtensionLowered = fileInfo.Extension.ToLower();
_Id = id;
_FullName = fileInfo.FullName;
_Name = fileInfo.Name;
_NameWithoutExtension = Path.GetFileNameWithoutExtension(fileInfo.FullName);
}
public FileHolder(string fileName) :
this(new FileInfo(fileName), null)
{ }
public FileHolder(string fileName, int? id) :
this(new FileInfo(fileName), id)
{ }
public override string ToString()
{
string result = JsonSerializer.Serialize(this, FileHolderSourceGenerationContext.Default.FileHolder);
return result;
}
private static FileHolder GetExisting(NginxFileSystem nginxFileSystem, int? id) =>
new(CreationTime: nginxFileSystem.LastModified,
DirectoryFullPath: Path.GetDirectoryName(nginxFileSystem.URI?.OriginalString ?? throw new Exception()),
Exists: true,
ExtensionLowered: Path.GetExtension(nginxFileSystem.Name).ToLower(),
FullName: nginxFileSystem.URI?.OriginalString ?? throw new Exception(),
Id: id,
LastWriteTime: nginxFileSystem.LastModified,
Length: nginxFileSystem.Length is null ? null : (long)nginxFileSystem.Length.Value,
Name: nginxFileSystem.Name,
NameWithoutExtension: Path.GetFileNameWithoutExtension(nginxFileSystem.Name));
public static FileHolder Get(NginxFileSystem nginxFileSystem) =>
GetExisting(nginxFileSystem, id: null);
private static FileHolder GetExisting(FileInfo fileInfo, int? id) =>
new(CreationTime: fileInfo.CreationTime,
DirectoryFullPath: fileInfo.DirectoryName,
Exists: fileInfo.Exists,
ExtensionLowered: fileInfo.Extension.ToLower(),
FullName: fileInfo.FullName,
Id: id,
LastWriteTime: fileInfo.LastWriteTime,
Length: fileInfo.Length,
Name: fileInfo.Name,
NameWithoutExtension: Path.GetFileNameWithoutExtension(fileInfo.FullName));
private static FileHolder GetNonExisting(FileInfo fileInfo, int? id) =>
new(CreationTime: null,
DirectoryFullPath: fileInfo.DirectoryName,
Exists: fileInfo.Exists,
ExtensionLowered: fileInfo.Extension.ToLower(),
FullName: fileInfo.FullName,
Id: id,
LastWriteTime: null,
Length: null,
Name: fileInfo.Name,
NameWithoutExtension: Path.GetFileNameWithoutExtension(fileInfo.FullName));
public static FileHolder Get(FileInfo fileInfo, int? id) =>
fileInfo.Exists ? GetExisting(fileInfo, id) : GetNonExisting(fileInfo, id);
public static FileHolder Get(FilePath filePath, int? id)
{
FileHolder result;
DateTime dateTime = new(filePath.CreationTicks);
result = new(CreationTime: dateTime,
DirectoryFullPath: filePath.DirectoryFullPath,
Exists: true,
ExtensionLowered: filePath.ExtensionLowered,
FullName: filePath.FullName,
Id: id,
LastWriteTime: new(filePath.LastWriteTicks),
Length: filePath.Length,
Name: filePath.Name,
NameWithoutExtension: Path.GetFileNameWithoutExtension(filePath.FullName));
return result;
}
private static FileHolder GetExisting(FileHolder fileHolder) =>
new(CreationTime: fileHolder.CreationTime,
DirectoryFullPath: fileHolder.DirectoryFullPath,
Exists: fileHolder.Exists,
ExtensionLowered: fileHolder.ExtensionLowered,
FullName: fileHolder.FullName,
Id: null,
LastWriteTime: fileHolder.LastWriteTime,
Length: fileHolder.Length,
Name: fileHolder.Name,
NameWithoutExtension: Path.GetFileNameWithoutExtension(fileHolder.FullName));
private static FileHolder GetNonExisting(FileHolder fileHolder) =>
new(CreationTime: null,
DirectoryFullPath: fileHolder.DirectoryFullPath,
Exists: fileHolder.Exists,
ExtensionLowered: fileHolder.ExtensionLowered,
FullName: fileHolder.FullName,
Id: null,
LastWriteTime: null,
Length: null,
Name: fileHolder.Name,
NameWithoutExtension: Path.GetFileNameWithoutExtension(fileHolder.FullName));
public static FileHolder Get(FileHolder fileHolder) =>
fileHolder.Exists ? GetExisting(fileHolder) : GetNonExisting(fileHolder);
public static FileHolder Get(string file) =>
Get(new FileInfo(file), id: null);
}
[JsonSourceGenerationOptions(WriteIndented = true)]

View File

@ -1,16 +1,23 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using View_by_Distance.Shared.Models.Stateless;
namespace View_by_Distance.Shared.Models;
public record FilePath(string DirectoryName,
public record FilePath(long CreationTicks,
string DirectoryFullPath,
string ExtensionLowered,
string FileNameFirstSegment,
string FullName,
int? Id,
bool IsIdFormat,
bool IsPaddedIdFormat,
bool? HasIgnoreKeyword,
bool? HasDateTimeOriginal,
bool IsIntelligentIdFormat,
long LastWriteTicks,
long Length,
string Name,
string NameWithoutExtension)
string NameWithoutExtension,
int? SortOrder)
{
public override string ToString()
@ -19,6 +26,81 @@ public record FilePath(string DirectoryName,
return result;
}
public static FilePath Get(ResultSettings resultSettings, MetadataSettings metadataSettings, FileHolder fileHolder, int? index)
{
if (fileHolder.CreationTime is null)
fileHolder = FileHolder.Get(fileHolder);
if (fileHolder.CreationTime is null)
throw new NullReferenceException(nameof(fileHolder.CreationTime));
if (fileHolder.LastWriteTime is null)
throw new NullReferenceException(nameof(fileHolder.LastWriteTime));
if (fileHolder.Length is null)
throw new NullReferenceException(nameof(fileHolder.Length));
FilePath result;
int? id;
int? sortOrder;
string fileNameFirstSegment = fileHolder.Name.Split('.')[0];
int sortOrderOnlyLengthIndex = metadataSettings.Offset.ToString().Length;
string fileDirectoryFullPath = fileHolder.DirectoryFullPath ?? throw new NullReferenceException();
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, sortOrder) = (null, null);
else if (isIntelligentIdFormat)
(id, sortOrder) = (IId.GetId(resultSettings, metadataSettings, fileNameFirstSegment), null);
else if (isPaddedIntelligentIdFormat)
{
if (!int.TryParse(fileNameFirstSegment[..sortOrderOnlyLengthIndex], out int absoluteValueOfSortOrder))
(id, sortOrder) = (null, null);
else
(id, sortOrder) = (IId.GetId(resultSettings, metadataSettings, fileNameFirstSegment[sortOrderOnlyLengthIndex..]), absoluteValueOfSortOrder);
}
else if (fileNameFirstSegmentIsIdFormat)
{
if (index is null)
throw new NullReferenceException(nameof(index));
if (!int.TryParse(fileNameFirstSegment, out int valueOfFileNameFirstSegment))
throw new NotSupportedException();
(id, sortOrder) = (valueOfFileNameFirstSegment, metadataSettings.Offset + index);
}
else
throw new NotSupportedException();
result = new(fileHolder.CreationTime.Value.Ticks,
fileDirectoryFullPath,
fileHolder.ExtensionLowered,
fileNameFirstSegment,
fileHolder.FullName,
id,
hasIgnoreKeyword,
hasDateTimeOriginal,
isIntelligentIdFormat,
fileHolder.LastWriteTime.Value.Ticks,
fileHolder.Length.Value,
fileHolder.Name,
fileHolder.NameWithoutExtension,
sortOrder);
return result;
}
public static FilePath Get(FilePath filePath, DeterministicHashCode deterministicHashCode) =>
new(filePath.CreationTicks,
filePath.DirectoryFullPath,
filePath.ExtensionLowered,
filePath.FileNameFirstSegment,
filePath.FullName,
deterministicHashCode.Id ?? filePath.Id,
filePath.HasIgnoreKeyword,
filePath.HasDateTimeOriginal,
filePath.IsIntelligentIdFormat,
filePath.LastWriteTicks,
filePath.Length,
filePath.Name,
filePath.NameWithoutExtension,
filePath.SortOrder);
}
[JsonSourceGenerationOptions(WriteIndented = true)]

View File

@ -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
{
}

View File

@ -0,0 +1,30 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace View_by_Distance.Shared.Models;
public record FirstPass(ExifDirectory ExifDirectory,
bool FastForwardMovingPictureExpertsGroupUsed,
MinimumYearAndPathCombined MinimumYearAndPathCombined,
FileHolder[] SidecarFiles)
{
public override string ToString()
{
string result = JsonSerializer.Serialize(this, FirstPassSourceGenerationContext.Default.FilePath);
return result;
}
}
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(FirstPass))]
public partial class FirstPassSourceGenerationContext : JsonSerializerContext
{
}
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(List<FirstPass>))]
public partial class FirstPassCollectionSourceGenerationContext : JsonSerializerContext
{
}

49
Shared/Models/Location.cs Normal file
View File

@ -0,0 +1,49 @@
using System.Text.Json.Serialization;
namespace View_by_Distance.Shared.Models;
[method: JsonConstructor]
public class Location(int bottom, double confidence, int left, int right, int top) : Properties.ILocation, IEquatable<Location>
{
public int Bottom { init; get; } = bottom;
public double Confidence { init; get; } = confidence;
public int Left { init; get; } = left;
public int Right { init; get; } = right;
public int Top { init; get; } = top;
public override bool Equals(object? obj) => Equals(obj as Location);
#pragma warning disable IDE0070
public override int GetHashCode()
#pragma warning restore IDE0070
{
int hashCode = -773114317;
hashCode = (hashCode * -1521134295) + Bottom.GetHashCode();
hashCode = (hashCode * -1521134295) + Left.GetHashCode();
hashCode = (hashCode * -1521134295) + Right.GetHashCode();
hashCode = (hashCode * -1521134295) + Top.GetHashCode();
return hashCode;
}
public bool Equals(Location? location)
{
return location is not null
&& Bottom == location.Bottom
&& Left == location.Left
&& Right == location.Right
&& Top == location.Top;
}
public static bool operator ==(Location location1, Location location2) => EqualityComparer<Location>.Default.Equals(location1, location2);
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.Location.Check(bottom, height, left, right, top, width, zCount, throwException: true);
return result;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,23 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace View_by_Distance.Shared.Models;
public record MakernoteDirectory(string? CameraSerialNumber,
string? FirmwareVersion,
string? QualityAndFileFormat)
{
public override string ToString()
{
string result = JsonSerializer.Serialize(this, MakernoteDirectorySourceGenerationContext.Default.MakernoteDirectory);
return result;
}
}
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(MakernoteDirectory))]
public partial class MakernoteDirectorySourceGenerationContext : JsonSerializerContext
{
}

Some files were not shown because too many files have changed in this diff Show More