Compare commits
47 Commits
c2757ef46b
...
07-06-b
Author | SHA1 | Date | |
---|---|---|---|
05fb8685d9 | |||
2c9b1a68c0 | |||
8f7fd02ba8 | |||
93598255c0 | |||
9a51d995cc | |||
23256c8152 | |||
1bbe583359 | |||
9fb6f0fa05 | |||
ae23e803fa | |||
c15c854481 | |||
ef4672aaf0 | |||
3ea4926f5e | |||
039355f31e | |||
68dff9c24c | |||
0fa899f65d | |||
17532d2698 | |||
1f8c4569ee | |||
0215e838e7 | |||
3ff8153393 | |||
7b8ab5508b | |||
368390b353 | |||
2f75667f20 | |||
924bd14c31 | |||
7b00fcaae8 | |||
fc891afe2c | |||
f6e6fa712f | |||
a878fe3253 | |||
2a305c69f0 | |||
8a817750c9 | |||
a9275dd5d1 | |||
dc1b0e3efd | |||
2661c4174c | |||
9521aee8c6 | |||
b9ed5ee159 | |||
cd88e340a3 | |||
c838848129 | |||
30b8e2f5a9 | |||
abbe2feac0 | |||
d1557e1d85 | |||
684ba1f0df | |||
7ec50dd56e | |||
d830c1d5a4 | |||
48eea2edbd | |||
1f00696bf3 | |||
82de27ce61 | |||
96c479e639 | |||
0310e06f3c |
@ -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
8
.gitignore
vendored
@ -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/.7-Question
|
0
.vscode/.yml
vendored
Normal file
0
.vscode/.yml
vendored
Normal file
526
.vscode/launch.json
vendored
526
.vscode/launch.json
vendored
@ -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
27
.vscode/mklink.md
vendored
@ -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
1
.vscode/read-me.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
# Read Me
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -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",
|
||||
|
587
.vscode/tasks.json
vendored
587
.vscode/tasks.json
vendored
@ -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,54 @@
|
||||
"/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"
|
||||
},
|
||||
{
|
||||
"label": "File-Folder-Helper AOT s X Day-Helper-2025-03-20",
|
||||
"type": "shell",
|
||||
"command": "L:/DevOps/Mesa_FI/File-Folder-Helper/bin/Release/net8.0/win-x64/publish/File-Folder-Helper.exe",
|
||||
"args": [
|
||||
"s",
|
||||
"X",
|
||||
"L:/Git/AA",
|
||||
"Day-Helper-2025-03-20",
|
||||
"false",
|
||||
"4"
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
34
AA.sln
34
AA.sln
@ -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
1
Compare/.vscode/read-me.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
# Read Me
|
58
Compare/AA.Compare.csproj
Normal file
58
Compare/AA.Compare.csproj
Normal file
@ -0,0 +1,58 @@
|
||||
<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>
|
||||
<HoursSinceNovember122024>$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1731369600)), 3600))))</HoursSinceNovember122024>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PackageId>Phares.View.by.Distance.Compare</PackageId>
|
||||
<Version>9.0.104.$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1731369600)), 3600))))</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.14" />
|
||||
<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>
|
285
Compare/Compare.cs
Normal file
285
Compare/Compare.cs
Normal file
@ -0,0 +1,285 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ShellProgressBar;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Drawing;
|
||||
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
|
||||
{
|
||||
|
||||
public long Ticks { get; init; }
|
||||
public int? CurrentTick => _ProgressBar?.CurrentTick;
|
||||
|
||||
private ProgressBar? _ProgressBar;
|
||||
private readonly ProgressBarOptions _ProgressBarOptions;
|
||||
|
||||
void ICompare.Tick() =>
|
||||
_ProgressBar?.Tick();
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
_ProgressBar?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
void ICompare.ConstructProgressBar(int maxTicks, string message)
|
||||
{
|
||||
_ProgressBar?.Dispose();
|
||||
_ProgressBar = new(maxTicks, message, _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;
|
||||
Ticks = DateTime.Now.Ticks;
|
||||
_ProgressBarOptions = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
|
||||
CompareWork(logger, appSettings, compare);
|
||||
}
|
||||
|
||||
private void CompareWork(ILogger<Program>? logger, AppSettings appSettings, ICompare compare)
|
||||
{
|
||||
const int updated = 0;
|
||||
DistanceLimits? distanceLimits;
|
||||
ReadOnlyCollection<LocationContainer> matrix;
|
||||
logger?.LogInformation("{Ticks}", compare.Ticks);
|
||||
ReadOnlyCollection<SaveContainer> saveContainers;
|
||||
ReadOnlyCollection<ExifDirectory> exifDirectories;
|
||||
ReadOnlyCollection<LocationContainer> preFiltered;
|
||||
ReadOnlyCollection<LocationContainer> postFiltered;
|
||||
ReadOnlyDictionary<string, LocationContainer> onlyOne;
|
||||
bool runToDoCollectionFirst = GetRunToDoCollectionFirst(appSettings, compare);
|
||||
ReadOnlyCollections readOnlyCollections = GetReadOnlyCollections(appSettings);
|
||||
ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mappedIdsThenWholePercentagesToLocationContainer = GetMappedIdsThenWholePercentagesToLocationContainer(appSettings, compare, readOnlyCollections);
|
||||
if (appSettings.CompareSettings.SaveExtractedFaces || appSettings.CompareSettings.SaveExtractedJavaScriptObjectNotation)
|
||||
SaveExtracted(appSettings, mappedIdsThenWholePercentagesToLocationContainer);
|
||||
foreach (string outputResolution in appSettings.CompareSettings.OutputResolutions)
|
||||
{
|
||||
if (runToDoCollectionFirst || outputResolution.Any(char.IsNumber))
|
||||
continue;
|
||||
logger?.LogInformation("{outputResolution}", outputResolution);
|
||||
exifDirectories = IFace.GetExifDirectories(appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.DistanceSettings, appSettings.CompareSettings, compare, outputResolution);
|
||||
preFiltered = IDistance.GetPreFilterLocationContainer(appSettings.DistanceSettings, appSettings.CompareSettings, compare, readOnlyCollections, mappedIdsThenWholePercentagesToLocationContainer, exifDirectories);
|
||||
if (preFiltered.Count == 0)
|
||||
continue;
|
||||
if (appSettings.CompareSettings.SaveExtractedFaces || appSettings.CompareSettings.SaveExtractedJavaScriptObjectNotation)
|
||||
SaveExtracted(appSettings, preFiltered);
|
||||
distanceLimits = new(appSettings.DistanceSettings);
|
||||
postFiltered = IDistance.GetPostFilterLocationContainer(preFiltered, distanceLimits);
|
||||
if (postFiltered.Count == 0)
|
||||
continue;
|
||||
matrix = IDistance.GetMatrixLocationContainers(appSettings.DistanceSettings, compare, mappedIdsThenWholePercentagesToLocationContainer, 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, outputResolution, onlyOne);
|
||||
if (saveContainers.Count == 0)
|
||||
continue;
|
||||
IDistance.SaveContainers(appSettings.DistanceSettings, appSettings.CompareSettings, compare, updated, saveContainers);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetRunToDoCollectionFirst(AppSettings appSettings, ICompare compare)
|
||||
{
|
||||
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(compare.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 ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> GetMappedIdsThenWholePercentagesToLocationContainer(AppSettings appSettings, ICompare compare, ReadOnlyCollections readOnlyCollections)
|
||||
{
|
||||
|
||||
ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> results;
|
||||
results = IDistance.GetMappedIdsThenWholePercentagesToLocationContainer(appSettings.ResultSettings,
|
||||
appSettings.MetadataSettings,
|
||||
appSettings.PeopleSettings,
|
||||
appSettings.DistanceSettings,
|
||||
appSettings.CompareSettings,
|
||||
compare,
|
||||
readOnlyCollections);
|
||||
if (results.Count == 0 && !appSettings.DistanceSettings.SaveSortingWithoutPerson)
|
||||
throw new NotSupportedException($"Switch {nameof(appSettings.DistanceSettings.SaveSortingWithoutPerson)}!");
|
||||
return results;
|
||||
}
|
||||
|
||||
private void SaveExtracted(AppSettings appSettings, ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mappedIdsThenWholePercentagesToLocationContainer)
|
||||
{
|
||||
ReadOnlyCollection<string>? paths;
|
||||
ReadOnlyDictionary<string, ReadOnlyCollection<string>> rootDirectoryFileNameToPaths = GetRootDirectoryFileNameToPaths(appSettings.ResultSettings, appSettings.CompareSettings);
|
||||
foreach (KeyValuePair<int, ReadOnlyDictionary<int, LocationContainer>> keyValuePair in mappedIdsThenWholePercentagesToLocationContainer)
|
||||
{
|
||||
foreach (KeyValuePair<int, LocationContainer> keyValue in keyValuePair.Value)
|
||||
{
|
||||
if (keyValue.Value.ExifDirectory is null || keyValue.Value.FaceFile?.Location is null)
|
||||
continue;
|
||||
if (rootDirectoryFileNameToPaths.TryGetValue(keyValue.Value.FilePath.FileNameFirstSegment, out paths))
|
||||
{
|
||||
if (appSettings.CompareSettings.SaveExtractedJavaScriptObjectNotation && keyValue.Value.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName is not null)
|
||||
SaveExtractedJavaScriptObjectNotation(keyValue.Key, keyValue.Value, paths);
|
||||
if (appSettings.CompareSettings.SaveExtractedFaces)
|
||||
SaveExtractedFace(keyValuePair.Key, keyValue.Key, keyValue.Value.ExifDirectory, keyValue.Value.FaceFile.Location, keyValue.Value.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveExtracted(AppSettings appSettings, ReadOnlyCollection<LocationContainer> preFiltered)
|
||||
{
|
||||
ReadOnlyCollection<string>? paths;
|
||||
ReadOnlyDictionary<string, ReadOnlyCollection<string>> rootDirectoryFileNameToPaths = GetRootDirectoryFileNameToPaths(appSettings.ResultSettings, appSettings.CompareSettings);
|
||||
foreach (LocationContainer locationContainer in preFiltered)
|
||||
{
|
||||
if (locationContainer?.FilePath?.Id is null || locationContainer?.WholePercentages is null || locationContainer?.ExifDirectory is null || locationContainer?.FaceFile?.Location is null)
|
||||
continue;
|
||||
if (rootDirectoryFileNameToPaths.TryGetValue(locationContainer.FilePath.FileNameFirstSegment, out paths))
|
||||
{
|
||||
if (appSettings.CompareSettings.SaveExtractedJavaScriptObjectNotation && locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName is not null)
|
||||
SaveExtractedJavaScriptObjectNotation(locationContainer.WholePercentages.Value, locationContainer, paths);
|
||||
if (appSettings.CompareSettings.SaveExtractedFaces)
|
||||
SaveExtractedFace(locationContainer.FilePath.Id.Value, locationContainer.WholePercentages.Value, locationContainer.ExifDirectory, locationContainer.FaceFile.Location, locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ReadOnlyDictionary<string, ReadOnlyCollection<string>> GetRootDirectoryFileNameToPaths(ResultSettings resultSettings, CompareSettings compareSettings)
|
||||
{
|
||||
Dictionary<string, ReadOnlyCollection<string>> results = [];
|
||||
string key;
|
||||
string extension;
|
||||
List<string>? collection;
|
||||
Dictionary<string, List<string>> keyValuePairs = [];
|
||||
string[] files = !compareSettings.SaveExtractedFaces && !compareSettings.SaveExtractedJavaScriptObjectNotation ? [] : Directory.GetFiles(resultSettings.RootDirectory, "*", SearchOption.AllDirectories);
|
||||
foreach (string file in files)
|
||||
{
|
||||
extension = Path.GetExtension(file);
|
||||
if (resultSettings.IgnoreExtensions.Contains(extension))
|
||||
continue;
|
||||
key = Path.GetFileNameWithoutExtension(file);
|
||||
if (!keyValuePairs.TryGetValue(key, out collection))
|
||||
{
|
||||
keyValuePairs.Add(key, []);
|
||||
if (!keyValuePairs.TryGetValue(key, out collection))
|
||||
throw new Exception();
|
||||
}
|
||||
collection.Add(file);
|
||||
}
|
||||
foreach (KeyValuePair<string, List<string>> keyValuePair in keyValuePairs)
|
||||
results.Add(keyValuePair.Key, keyValuePair.Value.AsReadOnly());
|
||||
return results.AsReadOnly();
|
||||
}
|
||||
|
||||
private static void SaveExtractedJavaScriptObjectNotation(int wholePercentages, LocationContainer locationContainer, ReadOnlyCollection<string> paths)
|
||||
{
|
||||
string file;
|
||||
string json;
|
||||
foreach (string path in paths)
|
||||
{
|
||||
json = locationContainer.GetWithoutEncoding();
|
||||
file = $"{path}-{wholePercentages}.json";
|
||||
_ = IPath.WriteAllText(file, json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveExtractedFace(int id, int wholePercentages, ExifDirectory _, Location location, PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName, ReadOnlyCollection<string> paths)
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
string person;
|
||||
foreach (string path in paths)
|
||||
{
|
||||
width = location.Right - location.Left;
|
||||
height = location.Bottom - location.Top;
|
||||
person = personKeyFormattedAndKeyTicksAndDisplayDirectoryName is null ? string.Empty : $"-{personKeyFormattedAndKeyTicksAndDisplayDirectoryName.DisplayDirectoryName}";
|
||||
ExtractFace(file: path,
|
||||
width: width,
|
||||
height: height,
|
||||
left: location.Left,
|
||||
top: location.Top,
|
||||
suffix: $"-{id}-{wholePercentages}{person}-face.jpg");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExtractFace(string file, float width, float height, double left, double top, string suffix)
|
||||
{
|
||||
RectangleF rectangle = new((float)left, (float)top, width, height);
|
||||
using Bitmap source = new(file);
|
||||
using Bitmap bitmap = new((int)width, (int)height);
|
||||
using (Graphics graphics = Graphics.FromImage(bitmap))
|
||||
graphics.DrawImage(source, new RectangleF(0, 0, width, height), rectangle, GraphicsUnit.Pixel);
|
||||
bitmap.Save($"{file}{suffix}");
|
||||
}
|
||||
|
||||
}
|
78
Compare/Models/AppSettings.cs
Normal file
78
Compare/Models/AppSettings.cs
Normal 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 is not 3 and not 6)
|
||||
throw new NullReferenceException(nameof(appSettings.DistanceSettings.RangeDaysDeltaTolerance));
|
||||
if (appSettings.DistanceSettings.RangeDistanceTolerance.Length is not 3 and not 6)
|
||||
throw new NullReferenceException(nameof(appSettings.DistanceSettings.RangeDistanceTolerance));
|
||||
if (appSettings.DistanceSettings.RangeFaceAreaTolerance.Length is not 3 and not 6)
|
||||
throw new NullReferenceException(nameof(appSettings.DistanceSettings.RangeFaceAreaTolerance));
|
||||
if (appSettings.DistanceSettings.RangeFaceConfidence.Length is not 3 and not 6)
|
||||
throw new NullReferenceException(nameof(appSettings.DistanceSettings.RangeFaceConfidence));
|
||||
_ = DateTime.Now.AddDays(-appSettings.DistanceSettings.RangeDaysDeltaTolerance[1]);
|
||||
if (appSettings.DistanceSettings.SaveSortingWithoutPerson && appSettings.PeopleSettings.JLinks.Where(l => !string.IsNullOrEmpty(l)).Any())
|
||||
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
|
||||
{
|
||||
}
|
29
Compare/Models/CompareSettings.cs
Normal file
29
Compare/Models/CompareSettings.cs
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
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,
|
||||
int MaxDegreeOfParallelism,
|
||||
string[] OutputResolutions,
|
||||
bool SaveExtractedFaces,
|
||||
bool SaveExtractedJavaScriptObjectNotation) : 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
|
||||
{
|
||||
}
|
32
Compare/Models/Identifier.cs
Normal file
32
Compare/Models/Identifier.cs
Normal 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
53
Compare/Program.cs
Normal 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
285
Distance/.editorconfig
Normal 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
1
Distance/.vscode/read-me.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
# Read Me
|
54
Distance/AA.Distance.csproj
Normal file
54
Distance/AA.Distance.csproj
Normal file
@ -0,0 +1,54 @@
|
||||
<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>
|
||||
<HoursSinceNovember122024>$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1731369600)), 3600))))</HoursSinceNovember122024>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PackageId>Phares.AA.Distance</PackageId>
|
||||
<Version>9.0.104.$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1731369600)), 3600))))</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>
|
3
Distance/Models/C_Resize.cs
Normal file
3
Distance/Models/C_Resize.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace View_by_Distance.Metadata.Models;
|
||||
|
||||
public class C_Resize() { }
|
3
Distance/Models/D2_FaceParts.cs
Normal file
3
Distance/Models/D2_FaceParts.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace View_by_Distance.Metadata.Models;
|
||||
|
||||
public class D2_FaceParts() { }
|
59
Distance/Models/DistanceLimits.cs
Normal file
59
Distance/Models/DistanceLimits.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
3
Distance/Models/E_Distance.cs
Normal file
3
Distance/Models/E_Distance.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace View_by_Distance.Metadata.Models;
|
||||
|
||||
public class E_Distance() { }
|
17
Distance/Models/IDistanceLimits.cs
Normal file
17
Distance/Models/IDistanceLimits.cs
Normal 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);
|
||||
|
||||
}
|
144
Distance/Models/Stateless/FaceEncodingLogic.cs
Normal file
144
Distance/Models/Stateless/FaceEncodingLogic.cs
Normal file
@ -0,0 +1,144 @@
|
||||
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 FaceEncodingLogic
|
||||
{
|
||||
|
||||
internal static void MoveUnableToMatch(FilePath filePath)
|
||||
{
|
||||
string checkFile = $"{filePath.FullName}.unk";
|
||||
if (File.Exists(filePath.FullName) && !File.Exists(checkFile))
|
||||
File.Move(filePath.FullName, checkFile);
|
||||
}
|
||||
|
||||
internal static ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> GetMappedIdsThenWholePercentagesToLocationContainer(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, ReadOnlyCollections readOnlyCollections)
|
||||
{
|
||||
ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> results;
|
||||
List<LocationContainer?> locationContainers = [];
|
||||
int maxDegreeOfParallelism = compareSettings.MaxDegreeOfParallelism;
|
||||
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
|
||||
ReadOnlyCollection<ExifDirectory> exifDirectories = MappedLogicA.GetMapped(resultSettings,
|
||||
metadataSettings,
|
||||
peopleSettings,
|
||||
distanceSettings,
|
||||
compareSettings,
|
||||
compare,
|
||||
readOnlyCollections);
|
||||
int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - compare.Ticks).TotalSeconds);
|
||||
string message = $") Building Mapped with Encoding Face Files Collection - {totalSeconds} total second(s)";
|
||||
compare.ConstructProgressBar(exifDirectories.Count, message);
|
||||
_ = Parallel.For(0, exifDirectories.Count, parallelOptions, (i, state) =>
|
||||
LocationContainersParallelFor(distanceSettings, compareSettings, compare, exifDirectories, i, locationContainers));
|
||||
results = GetMappedIdsThenWholePercentagesToLocationContainer(locationContainers);
|
||||
return results;
|
||||
}
|
||||
|
||||
private static void LocationContainersParallelFor(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, ReadOnlyCollection<ExifDirectory> exifDirectories, int i, List<LocationContainer?> results)
|
||||
{
|
||||
compare.Tick();
|
||||
ExifDirectory exifDirectory = exifDirectories[i];
|
||||
LocationContainer? locationContainer = GetLocationContainer(distanceSettings, compareSettings, exifDirectory);
|
||||
results.Add(locationContainer);
|
||||
}
|
||||
|
||||
internal static LocationContainer? GetLocationContainer(DistanceSettings distanceSettings, ICompareSettings compareSettings, ExifDirectory exifDirectory)
|
||||
{
|
||||
LocationContainer? result;
|
||||
string? json;
|
||||
DateOnly dateOnly;
|
||||
FaceFile? faceFile;
|
||||
int? wholePercentages;
|
||||
FaceEncoding? faceEncoding;
|
||||
FaceRecognitionDotNet.Models.FaceEncoding? encoding;
|
||||
wholePercentages = IMapping.GetWholePercentages(compareSettings, exifDirectory.FilePath);
|
||||
if (wholePercentages is null)
|
||||
{
|
||||
if (distanceSettings.DistanceMoveUnableToMatch)
|
||||
MoveUnableToMatch(exifDirectory.FilePath);
|
||||
result = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
faceFile = GetFaceFile(distanceSettings, exifDirectory);
|
||||
if (faceFile is null)
|
||||
result = null;
|
||||
else
|
||||
{
|
||||
dateOnly = DateOnly.FromDateTime(new DateTime(exifDirectory.FilePath.CreationTicks));
|
||||
json = Metadata.Models.Stateless.IMetadata.GetFaceEncoding(exifDirectory);
|
||||
faceEncoding = json is null ? null : JsonSerializer.Deserialize(json, FaceEncodingGenerationContext.Default.FaceEncoding);
|
||||
if (faceEncoding is null)
|
||||
result = null;
|
||||
else
|
||||
{
|
||||
encoding = FaceRecognitionDotNet.Models.FaceRecognition.LoadFaceEncoding(faceEncoding.RawEncoding);
|
||||
result = new(CreationDateOnly: dateOnly,
|
||||
ExifDirectory: exifDirectory,
|
||||
Encoding: encoding,
|
||||
FaceFile: faceFile,
|
||||
FilePath: exifDirectory.FilePath,
|
||||
LengthPermyriad: null,
|
||||
LengthSource: null,
|
||||
PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName: exifDirectory.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName,
|
||||
WholePercentages: wholePercentages);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static FaceFile? GetFaceFile(DistanceSettings distanceSettings, ExifDirectory exifDirectory)
|
||||
{
|
||||
FaceFile? result;
|
||||
string? json = Metadata.Models.Stateless.IMetadata.GetOutputResolution(exifDirectory);
|
||||
if (json is null || !json.Contains(nameof(DateTime)))
|
||||
{
|
||||
if (distanceSettings.DistanceMoveUnableToMatch)
|
||||
MoveUnableToMatch(exifDirectory.FilePath);
|
||||
result = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = JsonSerializer.Deserialize(json, FaceFileGenerationContext.Default.FaceFile);
|
||||
if (result is null || result.Location is null)
|
||||
{
|
||||
if (distanceSettings.DistanceMoveUnableToMatch)
|
||||
MoveUnableToMatch(exifDirectory.FilePath);
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> GetMappedIdsThenWholePercentagesToLocationContainer(List<LocationContainer?> locationContainers)
|
||||
{
|
||||
Dictionary<int, ReadOnlyDictionary<int, LocationContainer>> results = [];
|
||||
int id;
|
||||
int wholePercentages;
|
||||
Dictionary<int, LocationContainer>? keyValue;
|
||||
Dictionary<int, Dictionary<int, LocationContainer>> keyValuePairs = [];
|
||||
foreach (LocationContainer? locationContainer in locationContainers)
|
||||
{
|
||||
if (locationContainer?.FilePath.Id is null || locationContainer.WholePercentages is null)
|
||||
continue;
|
||||
id = locationContainer.FilePath.Id.Value;
|
||||
wholePercentages = locationContainer.WholePercentages.Value;
|
||||
if (!keyValuePairs.TryGetValue(id, out keyValue))
|
||||
{
|
||||
keyValuePairs.Add(id, []);
|
||||
if (!keyValuePairs.TryGetValue(id, out keyValue))
|
||||
throw new Exception();
|
||||
}
|
||||
keyValue.Add(wholePercentages, locationContainer);
|
||||
}
|
||||
foreach (KeyValuePair<int, Dictionary<int, LocationContainer>> keyValuePair in keyValuePairs)
|
||||
results.Add(keyValuePair.Key, keyValuePair.Value.AsReadOnly());
|
||||
return results.AsReadOnly();
|
||||
}
|
||||
|
||||
}
|
93
Distance/Models/Stateless/FilterLogicA.cs
Normal file
93
Distance/Models/Stateless/FilterLogicA.cs
Normal 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, ReadOnlyCollections readOnlyCollections, ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mappedIdsThenWholePercentagesToLocationContainer, ReadOnlyCollection<ExifDirectory> exifDirectories)
|
||||
{
|
||||
List<LocationContainer> results = [];
|
||||
string? json;
|
||||
string? model;
|
||||
bool? canReMap;
|
||||
bool? isFocusPerson;
|
||||
bool? inSkipCollection;
|
||||
FaceEncoding? faceEncoding;
|
||||
FaceRecognitionDotNet.Models.FaceEncoding? encoding;
|
||||
ReadOnlyDictionary<int, LocationContainer>? keyValues;
|
||||
List<FilePathAndWholePercentages>? wholePercentagesCollection;
|
||||
ReadOnlyCollection<LocationContainer> locationContainers = FilterLogicB.GetLocationContainers(distanceSettings, compareSettings, compare, exifDirectories, nameof(FilterLogicA));
|
||||
int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - compare.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 (mappedIdsThenWholePercentagesToLocationContainer.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));
|
||||
}
|
||||
return results.AsReadOnly();
|
||||
}
|
||||
|
||||
}
|
37
Distance/Models/Stateless/FilterLogicB.cs
Normal file
37
Distance/Models/Stateless/FilterLogicB.cs
Normal file
@ -0,0 +1,37 @@
|
||||
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.Distance.Models.Stateless;
|
||||
|
||||
internal static class FilterLogicB
|
||||
{
|
||||
|
||||
internal static ReadOnlyCollection<LocationContainer> GetLocationContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, ReadOnlyCollection<ExifDirectory> exifDirectories, string sourceClass)
|
||||
{
|
||||
List<LocationContainer> results = [];
|
||||
int maxDegreeOfParallelism = compareSettings.MaxDegreeOfParallelism;
|
||||
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
|
||||
int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - compare.Ticks).TotalSeconds);
|
||||
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) =>
|
||||
LocationContainersParallelFor(distanceSettings, compareSettings, compare, exifDirectories, i, results));
|
||||
return results.AsReadOnly();
|
||||
}
|
||||
|
||||
private static void LocationContainersParallelFor(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, ReadOnlyCollection<ExifDirectory> exifDirectories, int i, List<LocationContainer> results)
|
||||
{
|
||||
compare.Tick();
|
||||
ExifDirectory exifDirectory = exifDirectories[i];
|
||||
if (exifDirectory.FilePath.Id is null)
|
||||
return;
|
||||
LocationContainer? locationContainer = FaceEncodingLogic.GetLocationContainer(distanceSettings, compareSettings, exifDirectory);
|
||||
if (locationContainer is null)
|
||||
return;
|
||||
lock (results)
|
||||
results.Add(locationContainer);
|
||||
}
|
||||
|
||||
}
|
102
Distance/Models/Stateless/FilterLogicC.cs
Normal file
102
Distance/Models/Stateless/FilterLogicC.cs
Normal file
@ -0,0 +1,102 @@
|
||||
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(ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mappedIdsThenWholePercentagesToLocationContainer, ReadOnlyCollection<LocationContainer> postFiltered)
|
||||
{
|
||||
List<LocationContainer> results = [];
|
||||
foreach (LocationContainer locationContainer in postFiltered)
|
||||
results.Add(locationContainer);
|
||||
foreach (KeyValuePair<int, ReadOnlyDictionary<int, LocationContainer>> keyValuePair in mappedIdsThenWholePercentagesToLocationContainer)
|
||||
{
|
||||
foreach (KeyValuePair<int, LocationContainer> keyValue in keyValuePair.Value)
|
||||
results.Add(keyValue.Value);
|
||||
}
|
||||
return results.AsReadOnly();
|
||||
}
|
||||
|
||||
internal static ReadOnlyCollection<LocationContainer> GetMatrixLocationContainers(DistanceSettings distanceSettings, ICompare compare, ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mappedIdsThenWholePercentagesToLocationContainer, DistanceLimits distanceLimits, ReadOnlyCollection<LocationContainer> postFiltered)
|
||||
{
|
||||
List<LocationContainer> results = [];
|
||||
ReadOnlyCollection<LocationContainer> collection;
|
||||
ReadOnlyCollection<LocationContainer> locationContainers = GetCombined(mappedIdsThenWholePercentagesToLocationContainer, postFiltered);
|
||||
string message = $") Building Matrix - {(int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - compare.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();
|
||||
}
|
||||
|
||||
}
|
280
Distance/Models/Stateless/FilterLogicD.cs
Normal file
280
Distance/Models/Stateless/FilterLogicD.cs
Normal file
@ -0,0 +1,280 @@
|
||||
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 static string GetResizeContentDirectory(ResultSettings resultSettings, string cContentDirectory, FilePath filePath)
|
||||
{
|
||||
string result;
|
||||
CombinedEnumAndIndex cei = IPath.GetCombinedEnumAndIndex(resultSettings, filePath);
|
||||
result = Path.Combine(cContentDirectory, cei.Combined);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static string GetFacePartsDirectoryX(ResultSettings resultSettings, string d2FacePartsContentDirectory, FilePath filePath)
|
||||
{
|
||||
string result;
|
||||
CombinedEnumAndIndex cei = IPath.GetCombinedEnumAndIndex(resultSettings, filePath);
|
||||
result = Path.Combine(d2FacePartsContentDirectory, cei.Combined, filePath.NameWithoutExtension);
|
||||
return result;
|
||||
}
|
||||
|
||||
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 = IDistance.Get(saveIndividually, forceSingleImageHumanized, by.Value, isDefaultName);
|
||||
}
|
||||
result = new(byValue, isByMapping, isBySorting);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static void SaveContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, 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 - compare.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)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
internal static ReadOnlyCollection<SaveContainer> GetSaveContainers(ResultSettings resultSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, 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, compare.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 - compare.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();
|
||||
}
|
||||
|
||||
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 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);
|
||||
|
||||
}
|
65
Distance/Models/Stateless/IDistance.cs
Normal file
65
Distance/Models/Stateless/IDistance.cs
Normal file
@ -0,0 +1,65 @@
|
||||
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
|
||||
{
|
||||
|
||||
public static string Get(bool saveIndividually, string forceSingleImageHumanized, int by, bool isDefaultName) =>
|
||||
$"{by switch
|
||||
{
|
||||
IMapLogic.Mapping => nameof(IMapLogic.Mapping),
|
||||
IMapLogic.Sorting => saveIndividually ?
|
||||
nameof(IMapLogic.Individually) :
|
||||
nameof(IMapLogic.Sorting),
|
||||
IMapLogic.ForceSingleImage => forceSingleImageHumanized,
|
||||
_ => throw new NotImplementedException()
|
||||
}}{(!isDefaultName ? "-A" : "-Z")}";
|
||||
|
||||
public static ReadOnlyDictionary<string, LocationContainer> GetOnlyOne(DistanceSettings distanceSettings, ReadOnlyCollection<LocationContainer> matrix) =>
|
||||
FilterLogicC.GetOnlyOne(distanceSettings, matrix);
|
||||
|
||||
public static ReadOnlyCollection<LocationContainer> GetPostFilterLocationContainer(ReadOnlyCollection<LocationContainer> preFiltered, DistanceLimits distanceLimits) =>
|
||||
FilterLogicC.GetPostFilterLocationContainer(preFiltered, distanceLimits);
|
||||
|
||||
public static void SaveContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, int? updated, ReadOnlyCollection<SaveContainer> saveContainers) =>
|
||||
FilterLogicD.SaveContainers(distanceSettings, compareSettings, compare, updated, saveContainers);
|
||||
|
||||
public static ReadOnlyCollection<SaveContainer> GetSaveContainers(ResultSettings resultSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, string outputResolution, ReadOnlyDictionary<string, LocationContainer> onlyOne) =>
|
||||
FilterLogicD.GetSaveContainers(resultSettings, distanceSettings, compareSettings, compare, outputResolution, onlyOne);
|
||||
|
||||
public static ReadOnlyCollection<LocationContainer> GetMatrixLocationContainers(DistanceSettings distanceSettings, ICompare compare, ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mappedIdsThenWholePercentagesToLocationContainer, DistanceLimits distanceLimits, ReadOnlyCollection<LocationContainer> postFiltered) =>
|
||||
FilterLogicC.GetMatrixLocationContainers(distanceSettings, compare, mappedIdsThenWholePercentagesToLocationContainer, distanceLimits, postFiltered);
|
||||
|
||||
public static ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> GetMappedIdsThenWholePercentagesToLocationContainer(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, ReadOnlyCollections readOnlyCollections) =>
|
||||
FaceEncodingLogic.GetMappedIdsThenWholePercentagesToLocationContainer(resultSettings, metadataSettings, peopleSettings, distanceSettings, compareSettings, compare, readOnlyCollections);
|
||||
|
||||
public static ReadOnlyCollection<LocationContainer> GetPreFilterLocationContainer(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, ReadOnlyCollections readOnlyCollections, ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mappedIdsThenWholePercentagesToLocationContainer, ReadOnlyCollection<ExifDirectory> exifDirectories) =>
|
||||
FilterLogicA.GetPreFilterLocationContainer(distanceSettings, compareSettings, compare, readOnlyCollections, mappedIdsThenWholePercentagesToLocationContainer, exifDirectories);
|
||||
|
||||
internal static ReadOnlyDictionary<string, LocationContainer> TestStatic_GetOnlyOne(DistanceSettings distanceSettings, ReadOnlyCollection<LocationContainer> matrix) =>
|
||||
GetOnlyOne(distanceSettings, matrix);
|
||||
|
||||
internal static ReadOnlyCollection<LocationContainer> TestStatic_GetPostFilterLocationContainer(ReadOnlyCollection<LocationContainer> preFiltered, DistanceLimits distanceLimits) =>
|
||||
GetPostFilterLocationContainer(preFiltered, distanceLimits);
|
||||
|
||||
internal static void TestStatic_SaveContainers(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, int? updated, ReadOnlyCollection<SaveContainer> saveContainers) =>
|
||||
SaveContainers(distanceSettings, compareSettings, compare, updated, saveContainers);
|
||||
|
||||
internal static ReadOnlyCollection<SaveContainer> TestStatic_GetSaveContainers(ResultSettings resultSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, string outputResolution, ReadOnlyDictionary<string, LocationContainer> onlyOne) =>
|
||||
GetSaveContainers(resultSettings, distanceSettings, compareSettings, compare, outputResolution, onlyOne);
|
||||
|
||||
internal static ReadOnlyCollection<LocationContainer> TestStatic_GetMatrixLocationContainers(DistanceSettings distanceSettings, ICompare compare, ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mappedIdsThenWholePercentagesToLocationContainer, DistanceLimits distanceLimits, ReadOnlyCollection<LocationContainer> postFiltered) =>
|
||||
GetMatrixLocationContainers(distanceSettings, compare, mappedIdsThenWholePercentagesToLocationContainer, distanceLimits, postFiltered);
|
||||
|
||||
internal static ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> TestStatic_GetMappedIdsThenWholePercentagesToLocationContainer(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, ReadOnlyCollections readOnlyCollections) =>
|
||||
GetMappedIdsThenWholePercentagesToLocationContainer(resultSettings, metadataSettings, peopleSettings, distanceSettings, compareSettings, compare, readOnlyCollections);
|
||||
|
||||
internal static ReadOnlyCollection<LocationContainer> TestStatic_GetPreFilterLocationContainer(DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, ReadOnlyCollections readOnlyCollections, ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mappedIdsThenWholePercentagesToLocationContainer, ReadOnlyCollection<ExifDirectory> exifDirectories) =>
|
||||
GetPreFilterLocationContainer(distanceSettings, compareSettings, compare, readOnlyCollections, mappedIdsThenWholePercentagesToLocationContainer, exifDirectories);
|
||||
|
||||
}
|
178
Distance/Models/Stateless/MappedLogicA.cs
Normal file
178
Distance/Models/Stateless/MappedLogicA.cs
Normal file
@ -0,0 +1,178 @@
|
||||
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);
|
||||
|
||||
internal static ReadOnlyCollection<ExifDirectory> GetMapped(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, ReadOnlyCollections readOnlyCollections)
|
||||
{
|
||||
List<ExifDirectory> results = [];
|
||||
string eDistanceContentDirectory = Path.GetFullPath(IResult.GetResultsDateGroupDirectory(resultSettings, nameof(E_Distance), resultSettings.ResultContent));
|
||||
ReadOnlyCollection<MappedLogicB.Record> records = MappedLogicB.DeleteEmptyDirectoriesAndGetCollection(resultSettings, metadataSettings, peopleSettings, distanceSettings, compareSettings, compare, 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 - compare.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();
|
||||
}
|
||||
|
||||
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 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 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);
|
||||
}
|
||||
|
||||
}
|
529
Distance/Models/Stateless/MappedLogicB.cs
Normal file
529
Distance/Models/Stateless/MappedLogicB.cs
Normal file
@ -0,0 +1,529 @@
|
||||
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,
|
||||
bool? IsLocationContainerDebugDirectory,
|
||||
float? TotalDays);
|
||||
|
||||
internal static ReadOnlyCollection<Record> DeleteEmptyDirectoriesAndGetCollection(ResultSettings resultSettings, MetadataSettings metadataSettings, PeopleSettings peopleSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, 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;
|
||||
List<Record> records;
|
||||
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(IMapLogic.ManualCopy).Humanize(LetterCasing.Title);
|
||||
string forceSingleImageHumanized = nameof(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 - compare.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(IMapLogic.Sorting)) || personKeyFormatted.StartsWith(nameof(IMapLogic.Mapping)) || personKeyFormatted.StartsWith(nameof(IMapLogic.ManualCopy));
|
||||
if (!isReservedDirectoryName && personKeyFormatted.StartsWith(nameof(IMapLogic.Individually)))
|
||||
{
|
||||
Individually(compareSettings, ticksDirectory, personKeyFormattedDirectory);
|
||||
throw new Exception($"B) Move personKey directories up one from {nameof(IMapLogic.Sorting)} and delete {nameof(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);
|
||||
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(IMapLogic.Sorting)} and delete {nameof(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);
|
||||
}
|
||||
records = GetRecords(resultSettings, metadataSettings, compareSettings, ticksDirectory, isDefault, files, directoryNumber, personKeyFormatted, linksCount, distinct, personDisplayDirectoryName);
|
||||
if (records.Count > 0)
|
||||
results.AddRange(records);
|
||||
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();
|
||||
}
|
||||
|
||||
private static List<TicksDirectory> UpdateDateVerifyAndGetTicksDirectories(DistanceSettings distanceSettings, string eDistanceContentDirectory)
|
||||
{
|
||||
List<TicksDirectory> results = [];
|
||||
float? totalDays;
|
||||
long? next = null;
|
||||
string? checkDirectory;
|
||||
DateTime directoryDateTime;
|
||||
DirectoryInfo directoryInfo;
|
||||
TicksDirectory ticksDirectory;
|
||||
long? lastDirectoryTicks = null;
|
||||
DateTime dateTime = DateTime.Now;
|
||||
DateTime alternateDirectoryDateTime;
|
||||
string ticksDirectoryNameFirstSegment;
|
||||
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)
|
||||
{
|
||||
ticksDirectoryNameFirstSegment = Path.GetFileName(ticksFullPath).Split('.')[0];
|
||||
if (ticksDirectoryNameFirstSegment.Length < 3)
|
||||
continue;
|
||||
if (!long.TryParse(ticksDirectoryNameFirstSegment, 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 : ticksDirectoryNameFirstSegment.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), 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 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 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;
|
||||
}
|
||||
|
||||
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 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<Record> GetRecords(ResultSettings resultSettings, MetadataSettings metadataSettings, ICompareSettings compareSettings, TicksDirectory ticksDirectory, bool? isDefault, string[] files, int directoryNumber, string personKeyFormatted, int? linksCount, List<string> distinct, string? personDisplayDirectoryName)
|
||||
{
|
||||
List<Record> results = [];
|
||||
string @enum;
|
||||
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;
|
||||
}
|
||||
if (file.StartsWith(ticksDirectory.Directory))
|
||||
{
|
||||
@enum = IPath.GetEnum(filePath).ToString();
|
||||
if (!ticksDirectory.Directory.EndsWith(@enum))
|
||||
{
|
||||
checkFile = GetCheckFile(ticksDirectory, @enum, fileName, file);
|
||||
fileHolder = FileHolder.Get(checkFile);
|
||||
filePath = FilePath.Get(resultSettings, metadataSettings, fileHolder, index: null);
|
||||
if (filePath.Id is null)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
distinct.Add(fileName);
|
||||
record = new(DirectoryNumber: directoryNumber,
|
||||
IsDefault: isDefault,
|
||||
LinksCount: linksCount,
|
||||
MappedFaceFilePath: filePath,
|
||||
PersonDisplayDirectoryName: personDisplayDirectoryName,
|
||||
PersonKeyFormatted: personKeyFormatted);
|
||||
results.Add(record);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private static string GetCheckFile(TicksDirectory ticksDirectory, string @enum, string fileName, string file)
|
||||
{
|
||||
string result;
|
||||
string checkDirectory;
|
||||
string directory = file;
|
||||
List<string> collection = [];
|
||||
for (int i = 0; i < file.Length; i++)
|
||||
{
|
||||
directory = Path.GetDirectoryName(directory) ?? throw new Exception();
|
||||
if (directory == ticksDirectory.Directory)
|
||||
break;
|
||||
collection.Add(Path.GetFileName(directory));
|
||||
}
|
||||
collection.Reverse();
|
||||
checkDirectory = $"{ticksDirectory.Directory}.{@enum}";
|
||||
foreach (string directoryName in collection)
|
||||
checkDirectory = Path.Combine(checkDirectory, directoryName);
|
||||
if (!Directory.Exists(checkDirectory))
|
||||
_ = Directory.CreateDirectory(checkDirectory);
|
||||
result = Path.Combine(checkDirectory, fileName);
|
||||
if (File.Exists(result))
|
||||
throw new Exception($"File <{fileName}> already exists!");
|
||||
File.Move(file, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
1
Face/.vscode/read-me.md
vendored
Normal file
1
Face/.vscode/read-me.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
# Read Me
|
52
Face/AA.Face.csproj
Normal file
52
Face/AA.Face.csproj
Normal file
@ -0,0 +1,52 @@
|
||||
<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>
|
||||
<HoursSinceNovember122024>$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1731369600)), 3600))))</HoursSinceNovember122024>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PackageId>Phares.AA.Face</PackageId>
|
||||
<Version>9.0.104.$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1731369600)), 3600))))</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
3
Face/Models/D_Face.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace View_by_Distance.Metadata.Models;
|
||||
|
||||
public class D_Face() { }
|
55
Face/Models/Stateless/Face.cs
Normal file
55
Face/Models/Stateless/Face.cs
Normal 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, string outputResolution)
|
||||
{
|
||||
List<ExifDirectory> results = [];
|
||||
FileInfo fileInfo;
|
||||
int maxDegreeOfParallelism = compareSettings.MaxDegreeOfParallelism;
|
||||
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
|
||||
long? skipOlderThan = distanceSettings.SkipOlderThanDays < 1 ? null : new DateTime(compare.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();
|
||||
}
|
||||
|
||||
}
|
16
Face/Models/Stateless/IFace.cs
Normal file
16
Face/Models/Stateless/IFace.cs
Normal 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, string outputResolution) =>
|
||||
GetExifDirectories(resultSettings, metadataSettings, distanceSettings, compareSettings, compare, outputResolution);
|
||||
static ReadOnlyCollection<ExifDirectory> GetExifDirectories(ResultSettings resultSettings, MetadataSettings metadataSettings, DistanceSettings distanceSettings, ICompareSettings compareSettings, ICompare compare, string outputResolution) =>
|
||||
Face.GetExifDirectories(resultSettings, metadataSettings, distanceSettings, compareSettings, compare, outputResolution);
|
||||
|
||||
}
|
1
FaceRecognitionDotNet/.vscode/read-me.md
vendored
Normal file
1
FaceRecognitionDotNet/.vscode/read-me.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
# Read Me
|
54
FaceRecognitionDotNet/AA.FaceRecognitionDotNet.csproj
Normal file
54
FaceRecognitionDotNet/AA.FaceRecognitionDotNet.csproj
Normal file
@ -0,0 +1,54 @@
|
||||
<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>
|
||||
<HoursSinceNovember122024>$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1731369600)), 3600))))</HoursSinceNovember122024>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PackageId>Phares.AA.FaceRecognitionDotNet</PackageId>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<Version>9.0.104.$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1731369600)), 3600))))</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>
|
112
FaceRecognitionDotNet/Dlib/Python/CnnFaceDetectionModelV1.cs
Normal file
112
FaceRecognitionDotNet/Dlib/Python/CnnFaceDetectionModelV1.cs
Normal 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
|
||||
|
||||
}
|
130
FaceRecognitionDotNet/Dlib/Python/FaceRecognitionModelV1.cs
Normal file
130
FaceRecognitionDotNet/Dlib/Python/FaceRecognitionModelV1.cs
Normal 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
|
||||
|
||||
}
|
170
FaceRecognitionDotNet/Dlib/Python/SimpleObjectDetector.cs
Normal file
170
FaceRecognitionDotNet/Dlib/Python/SimpleObjectDetector.cs
Normal 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
|
||||
|
||||
}
|
27
FaceRecognitionDotNet/Extensions/FaceDetector.cs
Normal file
27
FaceRecognitionDotNet/Extensions/FaceDetector.cs
Normal 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
|
||||
|
||||
}
|
37
FaceRecognitionDotNet/Extensions/FaceLandmarkDetector.cs
Normal file
37
FaceRecognitionDotNet/Extensions/FaceLandmarkDetector.cs
Normal 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
|
||||
|
||||
}
|
97
FaceRecognitionDotNet/Models/DisposableObject.cs
Normal file
97
FaceRecognitionDotNet/Models/DisposableObject.cs
Normal 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
|
||||
|
||||
}
|
105
FaceRecognitionDotNet/Models/FaceEncoding.cs
Normal file
105
FaceRecognitionDotNet/Models/FaceEncoding.cs
Normal 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
|
||||
|
||||
}
|
496
FaceRecognitionDotNet/Models/FaceRecognition.cs
Normal file
496
FaceRecognitionDotNet/Models/FaceRecognition.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
6
FaceRecognitionDotNet/Models/FaceRecognitionGroup.cs
Normal file
6
FaceRecognitionDotNet/Models/FaceRecognitionGroup.cs
Normal 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);
|
22
FaceRecognitionDotNet/Models/FaceRecognitionModels.cs
Normal file
22
FaceRecognitionDotNet/Models/FaceRecognitionModels.cs
Normal 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";
|
||||
|
||||
}
|
129
FaceRecognitionDotNet/Models/Image.cs
Normal file
129
FaceRecognitionDotNet/Models/Image.cs
Normal 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
|
||||
|
||||
}
|
49
FaceRecognitionDotNet/Models/ModelParameter.cs
Normal file
49
FaceRecognitionDotNet/Models/ModelParameter.cs
Normal 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
|
||||
|
||||
}
|
107
FaceRecognitionDotNet/Models/Point.cs
Normal file
107
FaceRecognitionDotNet/Models/Point.cs
Normal 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
1
Metadata/.vscode/read-me.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
# Read Me
|
@ -1,43 +1,51 @@
|
||||
<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>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<HoursSinceNovember142023>$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1699920000)), 3600))))</HoursSinceNovember142023>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PackageId>Phares.AA.Metadata</PackageId>
|
||||
<Version>8.0.114.$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1699920000)), 3600))))</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="9.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Shared\AA.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,60 +1,64 @@
|
||||
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 ReadOnlyDictionary<int, ReadOnlyDictionary<string, string[]>> _FileGroups;
|
||||
private readonly ResultSettings _ResultSettings;
|
||||
private readonly MetadataSettings _MetadataSettings;
|
||||
private readonly ReadOnlyDictionary<int, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>> _ResultSingletonFileGroups;
|
||||
|
||||
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]);
|
||||
Dictionary<int, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>> results = [];
|
||||
ReadOnlyDictionary<int, ReadOnlyDictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>>> keyValuePairs = IPath.GetKeyValuePairs(resultSettings, aResultsFullGroupDirectory, [resultSettings.ResultSingleton]);
|
||||
foreach (KeyValuePair<int, ReadOnlyDictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>>> keyValuePair in keyValuePairs)
|
||||
{
|
||||
foreach (KeyValuePair<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>> keyValue in keyValuePair.Value)
|
||||
{
|
||||
if (keyValue.Key == resultSettings.ResultSingleton)
|
||||
results.Add(keyValuePair.Key, keyValue.Value);
|
||||
else
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
_ResultSingletonFileGroups = new(results);
|
||||
ReadOnlyCollection<string> directories = new([Path.Combine(aResultsFullGroupDirectory, resultSettings.ResultSingleton)]);
|
||||
IPath.CreateDirectories(directories);
|
||||
}
|
||||
|
||||
private FileInfo GetFileInfo(ResultConfiguration resultConfiguration, FilePath filePath)
|
||||
public (MinimumYearAndPathCombined, ExifDirectory) GetMetadataCollection(ResultSettings resultSettings, MetadataSettings metadataSettings, 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"));
|
||||
return result;
|
||||
}
|
||||
|
||||
public (FileInfo, ExifDirectory) GetMetadataCollection(MetadataConfiguration metadataConfiguration, FilePath filePath, DeterministicHashCode deterministicHashCode)
|
||||
{
|
||||
ExifDirectory? results;
|
||||
FileInfo fileInfo = GetFileInfo(metadataConfiguration.ResultConfiguration, filePath);
|
||||
if (_MetadataConfiguration.ForceMetadataLastWriteTimeToCreationTime && !fileInfo.Exists && File.Exists(Path.ChangeExtension(fileInfo.FullName, ".delete")))
|
||||
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 +66,118 @@ 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 MinimumYearAndPathCombined GetMinimumYearAndPathCombined(ResultSettings resultSettings, FilePath filePath)
|
||||
{
|
||||
return file =>
|
||||
MinimumYearAndPathCombined result;
|
||||
CombinedEnumAndIndex cei = IPath.GetCombinedEnumAndIndex(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(_ResultSingletonFileGroups[minimumYear][cei.Enum][cei.Index], $"{filePath.NameWithoutExtension}{filePath.ExtensionLowered}.json"));
|
||||
return result;
|
||||
}
|
||||
|
||||
private (int, string) GetJsonFile(ResultSettings resultSettings, FilePath filePath, ExifDirectory exifDirectory)
|
||||
{
|
||||
string? result;
|
||||
DateTime? dateTime;
|
||||
dateTime = IDate.GetDateTimeOriginal(exifDirectory);
|
||||
dateTime ??= IDate.GetMinimum(exifDirectory);
|
||||
CombinedEnumAndIndex cei = IPath.GetCombinedEnumAndIndex(resultSettings, filePath);
|
||||
int exifYear = dateTime.Value.Year < resultSettings.EpicYear ? resultSettings.EpicYear : dateTime.Value.Year;
|
||||
result = Path.Combine(_ResultSingletonFileGroups[exifYear][cei.Enum][cei.Index], $"{filePath.NameWithoutExtension}{filePath.ExtensionLowered}.json");
|
||||
return new(exifYear, result);
|
||||
}
|
||||
|
||||
private static (string, ExifDirectory?) Get(string jsonFile)
|
||||
{
|
||||
ExifDirectory? result;
|
||||
string json = File.ReadAllText(jsonFile);
|
||||
try
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
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, HttpClient? httpClient, FilePath filePath)
|
||||
{
|
||||
ExifDirectory result;
|
||||
MinimumYearAndPathCombined minimumYearAndPathCombined;
|
||||
if (httpClient is null)
|
||||
(minimumYearAndPathCombined, result) = GetMetadataCollection(resultSettings, metadataSettings, filePath);
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
private static Stream GetStream(HttpClient httpClient, FilePath filePath)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
[*.cs]
|
||||
csharp_preserve_single_line_statements = true
|
@ -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
|
||||
{
|
||||
}
|
@ -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
|
||||
{
|
||||
}
|
68
Metadata/Models/Stateless/Base.cs
Normal file
68
Metadata/Models/Stateless/Base.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
@ -1,32 +1,29 @@
|
||||
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[] { 0xff, 0xd8 }, DecodeJfif },
|
||||
{ new byte[] { 0x42, 0x4D }, DecodeBitmap },
|
||||
{ new byte[] { 0x52, 0x49, 0x46, 0x46 }, DecodeWebP },
|
||||
{ new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif },
|
||||
{ new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, DecodeGif },
|
||||
{ new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, DecodePng },
|
||||
{ new byte[] { 0xff, 0xd8 }, DecodeJfif },
|
||||
{ new byte[] { 0x52, 0x49, 0x46, 0x46 }, DecodeWebP },
|
||||
};
|
||||
#pragma warning restore IDE0230
|
||||
|
||||
private static bool StartsWith(byte[] thisBytes, byte[] thatBytes)
|
||||
private static bool StartsWith(List<byte> thisBytes, byte[] thatBytes)
|
||||
{
|
||||
for (int i = 0; i < thatBytes.Length; i += 1)
|
||||
for (int i = 0; i < thisBytes.Count && 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,85 @@ 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)
|
||||
Size? result;
|
||||
List<byte> magicBytes = [];
|
||||
int[] magicBytesLengths = (from l in _ImageFormatDecoders.Keys where l.Length <= binaryReader.BaseStream.Length orderby l.Length descending select l.Length).ToArray();
|
||||
if (magicBytesLengths.Length == 0)
|
||||
result = null;
|
||||
else
|
||||
{
|
||||
magicBytes[i] = binaryReader.ReadByte();
|
||||
|
||||
foreach (KeyValuePair<byte[], Func<BinaryReader, Size>> kvPair in _ImageFormatDecoders)
|
||||
result = null;
|
||||
if (binaryReader.BaseStream.Length == binaryReader.BaseStream.Position)
|
||||
_ = binaryReader.BaseStream.Seek(0, SeekOrigin.Begin);
|
||||
for (int i = 0; i < magicBytesLengths[0]; i++)
|
||||
{
|
||||
if (StartsWith(magicBytes, kvPair.Key))
|
||||
magicBytes.Add(binaryReader.ReadByte());
|
||||
foreach (KeyValuePair<byte[], Func<BinaryReader, Size?>> kvPair in _ImageFormatDecoders)
|
||||
{
|
||||
return kvPair.Value(binaryReader);
|
||||
if (StartsWith(magicBytes, kvPair.Key))
|
||||
{
|
||||
result = kvPair.Value(binaryReader);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result is not null)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException(_ErrorMessage, nameof(binaryReader));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
Size? result;
|
||||
using FileStream fileStream = File.OpenRead(path);
|
||||
using BinaryReader binaryReader = new(fileStream);
|
||||
result = GetDimensions(binaryReader);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static Size? GetDimensions(Stream stream)
|
||||
{
|
||||
Size? result;
|
||||
using BinaryReader binaryReader = new(stream);
|
||||
result = GetDimensions(binaryReader);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@ -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,544 @@ 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,
|
||||
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;
|
||||
}
|
||||
|
||||
|
53
Metadata/Models/Stateless/Face.cs
Normal file
53
Metadata/Models/Stateless/Face.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
148
Metadata/Models/Stateless/Get.cs
Normal file
148
Metadata/Models/Stateless/Get.cs
Normal 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(IWindows windows, ResultSettings resultSettings, MetadataSettings metadataSettings, 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(resultSettings, 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);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
bool fastForwardMovingPictureExpertsGroupUsed;
|
||||
MinimumYearAndPathCombined minimumYearAndPathCombined;
|
||||
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(resultSettings, 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
80
Metadata/Models/Stateless/IMetadata.cs
Normal file
80
Metadata/Models/Stateless/IMetadata.cs
Normal 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, A_Metadata metadata, List<string> distinct, List<MetadataGroup> metadataGroups) =>
|
||||
SetExifDirectoryCollection(windows, resultSettings, metadataSettings, metadata, distinct, metadataGroups);
|
||||
static Action<string> SetExifDirectoryCollection(IWindows windows, ResultSettings resultSettings, MetadataSettings metadataSettings, A_Metadata metadata, List<string> distinct, List<MetadataGroup> metadataGroups) =>
|
||||
Get.SetExifDirectoryCollection(windows, resultSettings, metadataSettings, metadata, distinct, metadataGroups);
|
||||
|
||||
}
|
@ -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
1
People/.vscode/read-me.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
# Read Me
|
52
People/AA.People.csproj
Normal file
52
People/AA.People.csproj
Normal file
@ -0,0 +1,52 @@
|
||||
<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>
|
||||
<HoursSinceNovember122024>$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1731369600)), 3600))))</HoursSinceNovember122024>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PackageId>Phares.AA.People</PackageId>
|
||||
<Version>9.0.104.$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1731369600)), 3600))))</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>
|
3
People/Models/A2_People.cs
Normal file
3
People/Models/A2_People.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace View_by_Distance.Metadata.Models;
|
||||
|
||||
public class A2_People() { }
|
40
People/Models/Stateless/IPeople.cs
Normal file
40
People/Models/Stateless/IPeople.cs
Normal 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);
|
||||
|
||||
}
|
558
People/Models/Stateless/People.cs
Normal file
558
People/Models/Stateless/People.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
110
People/Models/Stateless/ReadOnlyCollectionsLogic.cs
Normal file
110
People/Models/Stateless/ReadOnlyCollectionsLogic.cs
Normal 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
1
Rename/.vscode/read-me.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
# Read Me
|
@ -1,20 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<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>
|
||||
<HoursSinceNovember122024>$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1731369600)), 3600))))</HoursSinceNovember122024>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PackageId>Phares.View.by.Distance.Rename</PackageId>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<Version>8.0.101.1</Version>
|
||||
<Authors>Mike Phares</Authors>
|
||||
<Version>9.0.104.$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1731369600)), 3600))))</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 +40,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.8.2" />
|
||||
<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" />
|
||||
|
@ -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)]
|
||||
|
@ -1,2 +0,0 @@
|
||||
[*.cs]
|
||||
csharp_preserve_single_line_statements = true
|
@ -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
|
||||
{
|
||||
}
|
@ -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
|
||||
{
|
||||
}
|
32
Rename/Models/Identifier.cs
Normal file
32
Rename/Models/Identifier.cs
Normal 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
|
||||
{
|
||||
}
|
@ -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
|
||||
{
|
||||
}
|
39
Rename/Models/RenameSettings.cs
Normal file
39
Rename/Models/RenameSettings.cs
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace View_by_Distance.Rename.Models;
|
||||
|
||||
public record RenameSettings(string Company,
|
||||
string DefaultMaker,
|
||||
Dictionary<string, string?> DirectoryDictionary,
|
||||
string? FirstPassFile,
|
||||
bool ForceNewId,
|
||||
bool InPlace,
|
||||
bool InPlaceMoveDirectory,
|
||||
bool InPlaceWithOriginalName,
|
||||
bool JustMediaDate,
|
||||
int MaxDegreeOfParallelism,
|
||||
int MaxMilliSecondsPerCall,
|
||||
bool OnlySaveIdentifiersToDisk,
|
||||
string RelativePropertyCollectionFile,
|
||||
bool RequireRootDirectoryExists,
|
||||
string[] SidecarExtensions,
|
||||
bool SkipIdFiles,
|
||||
int ValidationImageDeterministicHashCodeId,
|
||||
string ValidationImageFile) : 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
|
||||
{
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
808
Rename/Rename.cs
808
Rename/Rename.cs
@ -1,89 +1,57 @@
|
||||
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.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);
|
||||
public long Ticks { get; init; }
|
||||
public int? CurrentTick => _ProgressBar?.CurrentTick;
|
||||
|
||||
private readonly AppSettings _AppSettings;
|
||||
private readonly RenameConfiguration _RenameConfiguration;
|
||||
private sealed record ToDo(string? Directory,
|
||||
FileInfo FileInfo,
|
||||
string File,
|
||||
bool JsonFile);
|
||||
|
||||
public Rename(List<string> args, ILogger<Program>? logger, IConfigurationRoot configurationRoot, AppSettings appSettings, bool isSilent, IConsole console)
|
||||
private sealed record Record(DateTime DateTime,
|
||||
ExifDirectory ExifDirectory,
|
||||
bool FastForwardMovingPictureExpertsGroupUsed,
|
||||
FileHolder[] SidecarFiles,
|
||||
bool HasDateTimeOriginal,
|
||||
bool HasIgnoreKeyword,
|
||||
string JsonFile);
|
||||
|
||||
private ProgressBar? _ProgressBar;
|
||||
private readonly ProgressBarOptions _ProgressBarOptions;
|
||||
|
||||
void IRename.Tick() =>
|
||||
_ProgressBar?.Tick();
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (isSilent)
|
||||
{ }
|
||||
if (args is null)
|
||||
throw new NullReferenceException(nameof(args));
|
||||
if (console is null)
|
||||
throw new NullReferenceException(nameof(console));
|
||||
_AppSettings = appSettings;
|
||||
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);
|
||||
}
|
||||
_ProgressBar?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
(ReadOnlyCollection<string>, FilePath?) IRename.ConvertAndGetFfmpegFiles(FilePath filePath)
|
||||
void IRename.ConstructProgressBar(int maxTicks, string message)
|
||||
{
|
||||
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
|
||||
{
|
||||
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));
|
||||
}
|
||||
return new(new(results), result);
|
||||
_ProgressBar?.Dispose();
|
||||
_ProgressBar = new(maxTicks, message, _ProgressBarOptions);
|
||||
}
|
||||
|
||||
#pragma warning disable CA1416
|
||||
|
||||
DeterministicHashCode IRename.GetDeterministicHashCode(FilePath filePath)
|
||||
{
|
||||
DeterministicHashCode result;
|
||||
@ -92,6 +60,7 @@ public class Rename : IRename
|
||||
int? height;
|
||||
try
|
||||
{
|
||||
#pragma warning disable CA1416
|
||||
using Image image = Image.FromFile(filePath.FullName);
|
||||
width = image.Width;
|
||||
height = image.Height;
|
||||
@ -103,6 +72,7 @@ public class Rename : IRename
|
||||
byte[] bytes = new byte[length];
|
||||
Marshal.Copy(intPtr, bytes, 0, length);
|
||||
bitmap.UnlockBits(bitmapData);
|
||||
#pragma warning restore CA1416
|
||||
id = IId.GetDeterministicHashCode(bytes);
|
||||
}
|
||||
catch (Exception)
|
||||
@ -115,122 +85,447 @@ public class Rename : IRename
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma warning restore CA1416
|
||||
|
||||
private void GetExifDirectoryCollection(IRename rename, List<(string, FileInfo, ExifDirectory)> exifDirectories, IEnumerable<string> files, A_Metadata metadata)
|
||||
ReadOnlyCollection<string> IRename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(ResultSettings resultSettings, FilePath filePath)
|
||||
{
|
||||
FileInfo fileInfo;
|
||||
FilePath filePath;
|
||||
FilePath? ffmpegFilePath;
|
||||
ExifDirectory exifDirectory;
|
||||
ReadOnlyCollection<string> ffmpegFiles;
|
||||
DeterministicHashCode deterministicHashCode;
|
||||
foreach (string file in files)
|
||||
List<string> results = [];
|
||||
bool isValidVideoFormatExtensions = resultSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered);
|
||||
if (isValidVideoFormatExtensions)
|
||||
{
|
||||
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);
|
||||
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 results.AsReadOnly();
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<Record> GetExifDirectoryCollection(List<(string, FileInfo, ExifDirectory)> exifDirectories)
|
||||
public Rename(List<string> args, ILogger<Program>? logger, AppSettings appSettings, bool isSilent, IConsole console)
|
||||
{
|
||||
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;
|
||||
if (isSilent)
|
||||
{ }
|
||||
if (args is null)
|
||||
throw new NullReferenceException(nameof(args));
|
||||
if (console is null)
|
||||
throw new NullReferenceException(nameof(console));
|
||||
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
|
||||
LogNetToHoursSince(logger);
|
||||
Ticks = DateTime.Now.Ticks;
|
||||
_ProgressBarOptions = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
|
||||
Verify(logger, appSettings, rename);
|
||||
RenameWork(logger, appSettings, rename);
|
||||
}
|
||||
|
||||
private static void Verify(ILogger<Program>? logger, AppSettings appSettings, IRename rename)
|
||||
{
|
||||
FileHolder fileHolder = FileHolder.Get(appSettings.RenameSettings.ValidationImageFile);
|
||||
FilePath filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, index: null);
|
||||
DeterministicHashCode deterministicHashCode = rename.GetDeterministicHashCode(filePath);
|
||||
if (deterministicHashCode.Id is null)
|
||||
throw new NullReferenceException(nameof(deterministicHashCode));
|
||||
if (deterministicHashCode.Id.Value != appSettings.RenameSettings.ValidationImageDeterministicHashCodeId)
|
||||
throw new Exception("Deterministic hash code id is incorrect!");
|
||||
logger?.LogDebug("Validated deterministic hash code id");
|
||||
}
|
||||
|
||||
private static void LogNetToHoursSince(ILogger<Program>? logger)
|
||||
{
|
||||
double secondsInAHour = 3600f;
|
||||
long epoch = new DateTime(1970, 1, 1).Ticks;
|
||||
long net8ReleaseDate = new DateTime(2023, 11, 14).Ticks;
|
||||
long net9ReleaseDate = new DateTime(2024, 11, 12).Ticks;
|
||||
double net8TotalSeconds = new TimeSpan(net8ReleaseDate - epoch).TotalSeconds;
|
||||
double net9TotalSeconds = new TimeSpan(net9ReleaseDate - epoch).TotalSeconds;
|
||||
logger?.LogInformation("It has been {net8TotalSeconds} seconds since net8 was released", net8TotalSeconds);
|
||||
logger?.LogInformation("It has been {net9TotalSeconds} seconds since net9 was released", net9TotalSeconds);
|
||||
double net8TotalHours = Math.Floor((DateTimeOffset.UtcNow.ToUnixTimeSeconds() - net8TotalSeconds) / secondsInAHour);
|
||||
double net9TotalHours = Math.Floor((DateTimeOffset.UtcNow.ToUnixTimeSeconds() - net9TotalSeconds) / secondsInAHour);
|
||||
logger?.LogInformation("It has been {net8TotalHours} hours since net8 was released", net8TotalHours);
|
||||
logger?.LogInformation("It has been {net9TotalHours} hours since net9 was released", net9TotalHours);
|
||||
}
|
||||
|
||||
private void RenameWork(ILogger<Program>? logger, AppSettings appSettings, IRename rename)
|
||||
{
|
||||
ReadOnlyCollection<int> ids = GetIds(appSettings.RenameSettings);
|
||||
string sourceDirectory = Path.GetFullPath(appSettings.ResultSettings.RootDirectory);
|
||||
if (!Directory.Exists(sourceDirectory))
|
||||
_ = Directory.CreateDirectory(sourceDirectory);
|
||||
logger?.LogInformation("{Ticks} {RootDirectory}", rename.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, ids, sourceDirectory, files);
|
||||
SaveIdentifiersToDisk(appSettings, rename, recordCollection);
|
||||
if (appSettings.RenameSettings.InPlace || appSettings.RenameSettings.InPlaceWithOriginalName)
|
||||
{
|
||||
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();
|
||||
if (recordCollection.Count > 0)
|
||||
recordCollection = new([]);
|
||||
string aMetadataSingletonDirectory = IResult.GetResultsGroupDirectory(appSettings.ResultSettings, nameof(A_Metadata));
|
||||
_ = IPath.DeleteEmptyDirectories(aMetadataSingletonDirectory);
|
||||
}
|
||||
results = GetExifDirectoryCollection(exifDirectories);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 static void VerifyIntMinValueLength(ReadOnlyCollection<Record> exifDirectories, int intMinValueLength)
|
||||
private ReadOnlyCollection<Record> GetRecordCollection(ILogger<Program>? logger, AppSettings appSettings, IRename rename, ReadOnlyCollection<int> ids, string sourceDirectory, ReadOnlyCollection<string> files)
|
||||
{
|
||||
foreach ((DateTime _, ExifDirectory exifDirectory, string _, string _) in exifDirectories)
|
||||
ReadOnlyCollection<Record> results;
|
||||
List<FirstPass> collection;
|
||||
string? checkFile = string.IsNullOrEmpty(appSettings.RenameSettings.FirstPassFile) ? null : Path.Combine(sourceDirectory, appSettings.RenameSettings.FirstPassFile);
|
||||
if (string.IsNullOrEmpty(checkFile) || !File.Exists(checkFile))
|
||||
collection = GetRecordCollection(logger, appSettings, rename, ids, sourceDirectory, files, checkFile);
|
||||
else
|
||||
{
|
||||
if (exifDirectory.Id is null)
|
||||
string json = File.ReadAllText(checkFile);
|
||||
collection = JsonSerializer.Deserialize(json, FirstPassCollectionSourceGenerationContext.Default.ListFirstPass) ?? throw new Exception();
|
||||
}
|
||||
if (appSettings.RenameSettings.JustMediaDate)
|
||||
results = new([]);
|
||||
else
|
||||
results = GetRecordCollection(appSettings, collection);
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<FirstPass> GetRecordCollection(ILogger<Program>? logger, AppSettings appSettings, IRename rename, ReadOnlyCollection<int> ids, string sourceDirectory, ReadOnlyCollection<string> files, string? checkFile)
|
||||
{
|
||||
List<FirstPass> results;
|
||||
FirstPass firstPass;
|
||||
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)
|
||||
{
|
||||
ReadOnlyDictionary<string, List<FileHolder>> keyValuePairs = IMetadata.GetKeyValuePairs(files);
|
||||
results = GetFirstPassCollection(logger, appSettings, rename, ids, metadata, keyValuePairs);
|
||||
}
|
||||
else
|
||||
{
|
||||
results = [];
|
||||
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.ResultSettings.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());
|
||||
results.Add(firstPass);
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(checkFile))
|
||||
{
|
||||
string json = JsonSerializer.Serialize(results, FirstPassCollectionSourceGenerationContext.Default.ListFirstPass);
|
||||
File.WriteAllText(Path.Combine(sourceDirectory, $"{rename.Ticks}.json"), json);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<FirstPass> GetFirstPassCollection(ILogger<Program>? logger, AppSettings appSettings, IRename rename, ReadOnlyCollection<int> ids, A_Metadata metadata, ReadOnlyDictionary<string, List<FileHolder>> keyValuePairs)
|
||||
{
|
||||
List<FirstPass> results = [];
|
||||
int index = -1;
|
||||
TimeSpan timeSpan;
|
||||
foreach (KeyValuePair<string, List<FileHolder>> keyValuePair in keyValuePairs)
|
||||
{
|
||||
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!");
|
||||
SetFirstPassCollection(logger, appSettings, rename, ids, metadata, index, keyValuePair, results);
|
||||
timeSpan = new(DateTime.Now.Ticks - rename.Ticks);
|
||||
if (timeSpan.TotalMilliseconds > appSettings.RenameSettings.MaxMilliSecondsPerCall)
|
||||
break;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private void SetFirstPassCollection(ILogger<Program>? logger, AppSettings appSettings, IRename rename, ReadOnlyCollection<int> ids, A_Metadata metadata, int index, KeyValuePair<string, List<FileHolder>> keyValuePair, List<FirstPass> results)
|
||||
{
|
||||
FilePath filePath;
|
||||
FirstPass firstPass;
|
||||
string directoryName;
|
||||
ExifDirectory exifDirectory;
|
||||
List<FileHolder> sidecarFiles;
|
||||
DeterministicHashCode deterministicHashCode;
|
||||
bool fastForwardMovingPictureExpertsGroupUsed;
|
||||
MinimumYearAndPathCombined minimumYearAndPathCombined;
|
||||
FilePath? fastForwardMovingPictureExpertsGroupFilePath;
|
||||
ReadOnlyCollection<string>? fastForwardMovingPictureExpertsGroupFiles;
|
||||
foreach (FileHolder fileHolder in keyValuePair.Value)
|
||||
{
|
||||
if (appSettings.RenameSettings.SidecarExtensions.Contains(fileHolder.ExtensionLowered))
|
||||
continue;
|
||||
if (intMinValueLength < exifDirectory.Id.Value.ToString().Length)
|
||||
throw new NotSupportedException();
|
||||
if (appSettings.ResultSettings.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.ResultSettings, 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 (appSettings.RenameSettings.JustMediaDate && filePath.HasDateTimeOriginal is not null && filePath.HasDateTimeOriginal.Value)
|
||||
JustMediaDate(logger, appSettings, metadata, filePath);
|
||||
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.ResultSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered))
|
||||
fastForwardMovingPictureExpertsGroupUsed = true;
|
||||
firstPass = new(exifDirectory, fastForwardMovingPictureExpertsGroupUsed, minimumYearAndPathCombined, sidecarFiles.ToArray());
|
||||
results.Add(firstPass);
|
||||
}
|
||||
}
|
||||
|
||||
private ReadOnlyCollection<ToDo> GetToDoCollection(ILogger<Program>? logger, long ticks, ReadOnlyCollection<Record> exifDirectories)
|
||||
private void JustMediaDate(ILogger<Program>? logger, AppSettings appSettings, A_Metadata metadata, FilePath filePath)
|
||||
{
|
||||
ExifDirectory? exifDirectory;
|
||||
MinimumYearAndPathCombined? minimumYearAndPathCombined;
|
||||
try
|
||||
{ (minimumYearAndPathCombined, exifDirectory) = metadata.GetMetadataCollection(appSettings.ResultSettings, appSettings.MetadataSettings, filePath); }
|
||||
catch (Exception)
|
||||
{
|
||||
exifDirectory = null;
|
||||
minimumYearAndPathCombined = null;
|
||||
logger?.LogWarning("<{filePath}>", filePath.FullName);
|
||||
}
|
||||
if (exifDirectory is null || minimumYearAndPathCombined is null)
|
||||
logger?.LogWarning("<{filePath}>", filePath.FullName);
|
||||
else
|
||||
{
|
||||
DateTime? dateTime = IDate.GetDateTimeOriginal(exifDirectory);
|
||||
if (dateTime is null)
|
||||
logger?.LogWarning("<{filePath}>", filePath.FullName);
|
||||
else
|
||||
{
|
||||
File.SetCreationTime(filePath.FullName, dateTime.Value);
|
||||
Thread.Sleep(500);
|
||||
File.SetLastWriteTime(filePath.FullName, dateTime.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void NonParallelismAndInPlace(AppSettings appSettings, IRename rename, ReadOnlyCollection<int> ids, ExifDirectory exifDirectory, MinimumYearAndPathCombined minimumYearAndPathCombined, bool fastForwardMovingPictureExpertsGroupUsed, FileHolder[] sidecarFiles)
|
||||
{
|
||||
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, exifDirectory.FilePath.ExtensionLowered, 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, toDoCollection.AsReadOnly());
|
||||
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 static ReadOnlyCollection<Record> GetRecordCollection(AppSettings appSettings, List<FirstPass> collection)
|
||||
{
|
||||
List<Record> results = [];
|
||||
Record record;
|
||||
DateTime? dateTime;
|
||||
bool hasIgnoreKeyword;
|
||||
bool hasDateTimeOriginal;
|
||||
ReadOnlyCollection<string> keywords;
|
||||
foreach (FirstPass firstPass in collection)
|
||||
{
|
||||
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 static void SaveIdentifiersToDisk(AppSettings appSettings, IRename rename, 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.ExifDirectory.FilePath.ExtensionLowered, 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, $"{rename.Ticks}.json"), json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null);
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<ToDo> GetToDoCollection(AppSettings appSettings, DirectoryInfo directoryInfo, ReadOnlyCollection<int> ids, ReadOnlyCollection<string> files, ReadOnlyCollection<Record> recordCollection)
|
||||
{
|
||||
List<ToDo> results = [];
|
||||
int season;
|
||||
string maker;
|
||||
ToDo? toDo;
|
||||
Record record;
|
||||
string jsonFile;
|
||||
string paddedId;
|
||||
string checkFile;
|
||||
string seasonName;
|
||||
string directoryName;
|
||||
FilePath filePath;
|
||||
DateTime? dateTime;
|
||||
FileInfo[] matches;
|
||||
FileHolder fileHolder;
|
||||
bool? hasIgnoreKeyword;
|
||||
string? checkDirectory;
|
||||
CombinedEnumAndIndex cei;
|
||||
bool? hasDateTimeOriginal;
|
||||
const string jpg = ".jpg";
|
||||
string checkFileExtension;
|
||||
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++)
|
||||
ReadOnlyCollection<string> keywords;
|
||||
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.ExifDirectory.FilePath.ExtensionLowered, 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 +533,208 @@ 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");
|
||||
if (record.ExifDirectory.FilePath.HasDateTimeOriginal is not null && record.ExifDirectory.FilePath.HasIgnoreKeyword is not null)
|
||||
{
|
||||
hasIgnoreKeyword = record.ExifDirectory.FilePath.HasIgnoreKeyword;
|
||||
hasDateTimeOriginal = record.ExifDirectory.FilePath.HasDateTimeOriginal;
|
||||
}
|
||||
else
|
||||
{
|
||||
dateTime = IDate.GetDateTimeOriginal(record.ExifDirectory);
|
||||
hasDateTimeOriginal = dateTime is not null;
|
||||
if (dateTime is null && appSettings.ResultSettings.ValidVideoFormatExtensions.Contains(record.ExifDirectory.FilePath.ExtensionLowered))
|
||||
continue;
|
||||
keywords = IMetadata.GetKeywords(record.ExifDirectory);
|
||||
hasIgnoreKeyword = appSettings.MetadataSettings.IgnoreRulesKeyWords.Any(keywords.Contains);
|
||||
}
|
||||
cei = IPath.GetCombinedEnumAndIndex(appSettings.ResultSettings, record.ExifDirectory.FilePath);
|
||||
jsonFile = Path.Combine(jsonFileSubDirectory, cei.Combined, $"{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);
|
||||
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 results.AsReadOnly();
|
||||
}
|
||||
|
||||
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 void VerifyIntMinValueLength(MetadataSettings metadataSettings, ReadOnlyCollection<Record> recordCollection)
|
||||
{
|
||||
foreach (Record record in recordCollection)
|
||||
{
|
||||
if (record.ExifDirectory.FilePath.Id is null)
|
||||
continue;
|
||||
if (metadataSettings.IntMinValueLength < record.ExifDirectory.FilePath.Id.Value.ToString().Length)
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
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 directoryName;
|
||||
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]}";
|
||||
if (!string.IsNullOrEmpty(splat) || isWrongYear is null || isWrongYear.Value || appSettings.RenameSettings.DirectoryDictionary.Count < 2)
|
||||
directoryName = GetDirectoryName(year, tfw, segments[0], splat, seasonValue, seasonName, makerSplit);
|
||||
else
|
||||
{
|
||||
directoryName = record.DateTime.ToString("yyyy-MM-dd");
|
||||
if (appSettings.RenameSettings.DirectoryDictionary.TryGetValue(directoryName, out string? value) && !string.IsNullOrEmpty(value))
|
||||
directoryName = value;
|
||||
}
|
||||
result = Path.GetFullPath(Path.Combine(rootDirectory, contains, directoryName));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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 List<ToDo> GetSidecarFiles(AppSettings appSettings, Record record, List<string> distinct, string checkDirectory, string paddedId)
|
||||
{
|
||||
List<ToDo> results = [];
|
||||
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);
|
||||
results.Add(new(checkDirectory, fileHolder, checkFile, JsonFile: false));
|
||||
toDo = new(Directory: checkDirectory,
|
||||
FileInfo: new(filePath.FullName),
|
||||
File: checkFile,
|
||||
JsonFile: false);
|
||||
results.Add(toDo);
|
||||
}
|
||||
return new(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
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)
|
||||
rename?.Tick();
|
||||
if (!toDo.FileInfo.Exists)
|
||||
continue;
|
||||
if (toDo.JsonFile)
|
||||
{
|
||||
if (File.Exists(toDo.File))
|
||||
File.Delete(toDo.File);
|
||||
try
|
||||
{ File.Move(toDo.FileInfo.FullName, toDo.File); }
|
||||
catch (Exception)
|
||||
{ continue; }
|
||||
}
|
||||
else if (toDo.Directory is null)
|
||||
throw new NotSupportedException();
|
||||
else
|
||||
{
|
||||
if (File.Exists(toDo.File))
|
||||
File.Delete(toDo.File);
|
||||
try
|
||||
{ File.Move(toDo.FileInfo.FullName, toDo.File); }
|
||||
catch (Exception)
|
||||
{ continue; }
|
||||
results.Add($"{toDo.FileInfo.FullName}\t{toDo.File}");
|
||||
}
|
||||
}
|
||||
return results.AsReadOnly();
|
||||
}
|
||||
|
||||
private static void VerifyDirectories(ReadOnlyCollection<ToDo> toDoCollection)
|
||||
@ -263,29 +750,4 @@ public class Rename : IRename
|
||||
}
|
||||
}
|
||||
|
||||
private ReadOnlyCollection<string> RenameFilesInDirectories(ReadOnlyCollection<ToDo> toDoCollection)
|
||||
{
|
||||
List<string> results = [];
|
||||
VerifyDirectories(toDoCollection);
|
||||
foreach (ToDo toDo in toDoCollection)
|
||||
{
|
||||
if (toDo.JsonFile)
|
||||
{
|
||||
if (File.Exists(toDo.File))
|
||||
File.Delete(toDo.File);
|
||||
File.Move(toDo.FileHolder.FullName, toDo.File);
|
||||
}
|
||||
else if (toDo.Directory is null)
|
||||
throw new NotSupportedException();
|
||||
else
|
||||
{
|
||||
if (File.Exists(toDo.File))
|
||||
File.Delete(toDo.File);
|
||||
File.Move(toDo.FileHolder.FullName, toDo.File);
|
||||
results.Add($"{toDo.FileHolder.FullName}\t{toDo.File}");
|
||||
}
|
||||
}
|
||||
return new(results);
|
||||
}
|
||||
|
||||
}
|
1
Shared/.vscode/read-me.md
vendored
Normal file
1
Shared/.vscode/read-me.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
# Read Me
|
@ -1,38 +1,45 @@
|
||||
<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>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<HoursSinceNovember142023>$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1699920000)), 3600))))</HoursSinceNovember142023>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PackageId>Phares.AA.Shared</PackageId>
|
||||
<Version>8.0.114.$([System.Math]::Floor($([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()), 1699920000)), 3600))))</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="9.0.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
23
Shared/Models/CombinedEnumAndIndex.cs
Normal file
23
Shared/Models/CombinedEnumAndIndex.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace View_by_Distance.Shared.Models;
|
||||
|
||||
public record CombinedEnumAndIndex(string Combined,
|
||||
byte Enum,
|
||||
int Index)
|
||||
{
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string result = JsonSerializer.Serialize(this, CombinedEnumAndIndexSourceGenerationContext.Default.CombinedEnumAndIndex);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(CombinedEnumAndIndex))]
|
||||
internal partial class CombinedEnumAndIndexSourceGenerationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
44
Shared/Models/DistanceSettings.cs
Normal file
44
Shared/Models/DistanceSettings.cs
Normal 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
|
||||
{
|
||||
}
|
@ -3,20 +3,21 @@ 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,
|
||||
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)
|
||||
{
|
||||
|
||||
@ -28,7 +29,7 @@ public record ExifDirectory(AviDirectory AviDirectory,
|
||||
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonSerializable(typeof(ExifDirectory))]
|
||||
public partial class ExifDirectorySourceGenerationContext : JsonSerializerContext
|
||||
{
|
||||
|
53
Shared/Models/FaceDistance.cs
Normal file
53
Shared/Models/FaceDistance.cs
Normal 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
|
||||
{
|
||||
}
|
11
Shared/Models/FaceEncoding.cs
Normal file
11
Shared/Models/FaceEncoding.cs
Normal 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
33
Shared/Models/FaceFile.cs
Normal 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
|
||||
{
|
||||
}
|
22
Shared/Models/FacePartAndFacePointArray.cs
Normal file
22
Shared/Models/FacePartAndFacePointArray.cs
Normal 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
|
||||
{
|
||||
}
|
39
Shared/Models/FacePoint.cs
Normal file
39
Shared/Models/FacePoint.cs
Normal 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);
|
||||
|
||||
}
|
22
Shared/Models/FaceRecognitionGroup.cs
Normal file
22
Shared/Models/FaceRecognitionGroup.cs
Normal 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
|
||||
{
|
||||
}
|
@ -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)]
|
||||
|
@ -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)]
|
||||
|
21
Shared/Models/FilePathAndWholePercentages.cs
Normal file
21
Shared/Models/FilePathAndWholePercentages.cs
Normal 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
|
||||
{
|
||||
}
|
30
Shared/Models/FirstPass.cs
Normal file
30
Shared/Models/FirstPass.cs
Normal 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
49
Shared/Models/Location.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
77
Shared/Models/LocationContainer.cs
Normal file
77
Shared/Models/LocationContainer.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
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,
|
||||
int? WholePercentages)
|
||||
{
|
||||
|
||||
public string GetWithoutEncoding()
|
||||
{
|
||||
string result;
|
||||
WithoutEncoding withoutEncoding = new(CreationDateOnly: CreationDateOnly,
|
||||
ExifDirectory: ExifDirectory,
|
||||
FaceFile: FaceFile,
|
||||
FilePath: FilePath,
|
||||
LengthPermyriad: LengthPermyriad,
|
||||
LengthSource: LengthSource,
|
||||
PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName: PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName,
|
||||
WholePercentages: WholePercentages);
|
||||
result = JsonSerializer.Serialize(withoutEncoding, WithoutEncodingSourceGenerationContext.Default.WithoutEncoding);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static LocationContainer Get(LocationContainer locationContainer, object? encoding)
|
||||
{
|
||||
LocationContainer result;
|
||||
result = new(CreationDateOnly: locationContainer.CreationDateOnly,
|
||||
ExifDirectory: locationContainer.ExifDirectory,
|
||||
Encoding: encoding,
|
||||
FaceFile: locationContainer.FaceFile,
|
||||
FilePath: locationContainer.FilePath,
|
||||
LengthPermyriad: locationContainer.LengthPermyriad,
|
||||
LengthSource: locationContainer.LengthSource,
|
||||
PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName: locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName,
|
||||
WholePercentages: locationContainer.WholePercentages);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static LocationContainer Get(LocationContainer source, LocationContainer locationContainer, int lengthPermyriad, bool keepExifDirectory, bool keepEncoding)
|
||||
{
|
||||
LocationContainer result;
|
||||
result = new(CreationDateOnly: locationContainer.CreationDateOnly,
|
||||
ExifDirectory: keepExifDirectory ? locationContainer.ExifDirectory : null,
|
||||
Encoding: keepEncoding ? locationContainer.Encoding : null,
|
||||
FaceFile: locationContainer.FaceFile,
|
||||
FilePath: locationContainer.FilePath,
|
||||
LengthPermyriad: lengthPermyriad,
|
||||
LengthSource: source.FilePath,
|
||||
PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName: locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName,
|
||||
WholePercentages: locationContainer.WholePercentages);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal record WithoutEncoding(DateOnly? CreationDateOnly,
|
||||
ExifDirectory? ExifDirectory,
|
||||
FaceFile? FaceFile,
|
||||
FilePath FilePath,
|
||||
int? LengthPermyriad,
|
||||
FilePath? LengthSource,
|
||||
PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName,
|
||||
int? WholePercentages);
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonSerializable(typeof(WithoutEncoding))]
|
||||
internal partial class WithoutEncodingSourceGenerationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user