Changed GetDimensions to handle a stream at the end and one exit Switched to using Action? over IDlibDotNet for Tick method Switched to using AsReadOnly over new() Moved Meta Base to Shared
307 lines
15 KiB
C#
307 lines
15 KiB
C#
using MetadataExtractor;
|
|
using System.Collections.ObjectModel;
|
|
using System.Text.Json;
|
|
using View_by_Distance.Shared.Models;
|
|
using View_by_Distance.Shared.Models.Methods;
|
|
using View_by_Distance.Shared.Models.Properties;
|
|
using View_by_Distance.Shared.Models.Stateless.Methods;
|
|
|
|
namespace View_by_Distance.Metadata.Models;
|
|
|
|
/// <summary>
|
|
// Dictionary<string, List<KeyValuePair<string, string>>>
|
|
/// </summary>
|
|
public class B_Metadata : IMetadata<MetadataExtractor.Directory>
|
|
{
|
|
|
|
private readonly Dictionary<int, ExifDirectory> _ExifDirectoriesById;
|
|
|
|
// First
|
|
// Set DateGroupDirectory
|
|
// Create a directories for Singleton
|
|
// Populate Collection
|
|
// Populate Singleton
|
|
// Populate existing ExifDirectories
|
|
// Run similar for resize
|
|
// Second
|
|
// Populate needed ExifDirectories Dictionary can't be init only
|
|
|
|
private readonly bool _PropertiesChangedForMetadata;
|
|
private readonly IPropertyConfiguration _PropertyConfiguration;
|
|
private readonly bool _ForceMetadataLastWriteTimeToCreationTime;
|
|
private readonly JsonSerializerOptions _WriteIndentedJsonSerializerOptions;
|
|
private readonly ReadOnlyDictionary<byte, ReadOnlyCollection<string>>[] _ResultSingletonFileGroups;
|
|
|
|
public B_Metadata(IDlibDotNet? dlibDotNet, IPropertyConfiguration propertyConfiguration, bool forceMetadataLastWriteTimeToCreationTime, bool propertiesChangedForMetadata, long ticks, string bResultsFullGroupDirectory)
|
|
{
|
|
_PropertyConfiguration = propertyConfiguration;
|
|
_PropertiesChangedForMetadata = propertiesChangedForMetadata;
|
|
_ForceMetadataLastWriteTimeToCreationTime = forceMetadataLastWriteTimeToCreationTime;
|
|
_ResultSingletonFileGroups = [new(new Dictionary<byte, ReadOnlyCollection<string>>())];
|
|
_WriteIndentedJsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true };
|
|
ReadOnlyDictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>> keyValuePairs = Shared.Models.Stateless.Methods.IPath.GetKeyValuePairs(propertyConfiguration, bResultsFullGroupDirectory, [propertyConfiguration.ResultSingleton]);
|
|
foreach (KeyValuePair<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>> keyValuePair in keyValuePairs)
|
|
{
|
|
if (keyValuePair.Key == _PropertyConfiguration.ResultSingleton)
|
|
_ResultSingletonFileGroups[0] = keyValuePair.Value;
|
|
else
|
|
throw new Exception();
|
|
}
|
|
const string extension = ".json";
|
|
const string fileSearchFilter = "*";
|
|
string filesCollectionRootDirectory;
|
|
const string directorySearchFilter = "*";
|
|
Dictionary<int, ExifDirectory> exifDirectoriesById = [];
|
|
int maxDegreeOfParallelism = Environment.ProcessorCount;
|
|
Action? tick = dlibDotNet is null ? null : dlibDotNet.Tick;
|
|
filesCollectionRootDirectory = propertyConfiguration.RootDirectory;
|
|
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
|
|
string jsonGroupSingletonDirectory = Path.Combine(bResultsFullGroupDirectory, propertyConfiguration.ResultSingleton);
|
|
string jsonGroupCollectionDirectory = Path.Combine(bResultsFullGroupDirectory, propertyConfiguration.ResultCollection);
|
|
ReadOnlyCollection<string> directories = new([jsonGroupSingletonDirectory, jsonGroupCollectionDirectory]);
|
|
Shared.Models.Stateless.Methods.IPath.CreateDirectories(directories);
|
|
ReadOnlyCollection<ReadOnlyCollection<FilePath>> filePathsCollection = IDirectory.GetFilePathCollections(propertyConfiguration, directorySearchFilter, fileSearchFilter, filesCollectionRootDirectory, useIgnoreExtensions: true, useCeilingAverage: false);
|
|
ReadOnlyCollection<FilePair> filePairs = IFilePair.GetFilePairs(propertyConfiguration, directorySearchFilter, extension, jsonGroupSingletonDirectory, filePathsCollection);
|
|
string message = $") {nameof(B_Metadata)} - Preloading ExifDirectory Dictionary - {(int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds)} total second(s)";
|
|
dlibDotNet?.ConstructProgressBar(filePairs.Count, message);
|
|
_ = Parallel.For(0, filePairs.Count, parallelOptions, (i, state) => ParallelFor(filePairs[i], exifDirectoriesById, tick));
|
|
_ExifDirectoriesById = exifDirectoriesById;
|
|
}
|
|
|
|
public ReadOnlyDictionary<int, ExifDirectory> GetKeyValuePairsAndClear()
|
|
{
|
|
Dictionary<int, ExifDirectory> results = [];
|
|
foreach (KeyValuePair<int, ExifDirectory> keyValuePair in _ExifDirectoriesById)
|
|
results.Add(keyValuePair.Key, keyValuePair.Value);
|
|
_ExifDirectoriesById.Clear();
|
|
return results.AsReadOnly();
|
|
}
|
|
|
|
private void ParallelFor(FilePair filePair, Dictionary<int, ExifDirectory> results, Action? tick)
|
|
{
|
|
tick?.Invoke();
|
|
if (filePair.FilePath.Id is null)
|
|
return;
|
|
ExifDirectory? exifDirectory = GetExifDirectory(filePair);
|
|
if (exifDirectory is null)
|
|
return;
|
|
lock (results)
|
|
{
|
|
if (!results.ContainsKey(filePair.FilePath.Id.Value))
|
|
results.Add(filePair.FilePath.Id.Value, exifDirectory);
|
|
}
|
|
}
|
|
|
|
private static ExifDirectory? GetExifDirectory(FilePair filePair)
|
|
{
|
|
ExifDirectory? result;
|
|
if (filePair.Match is null)
|
|
result = null;
|
|
else
|
|
{
|
|
string json;
|
|
json = File.ReadAllText(filePair.Match.FullName);
|
|
if (string.IsNullOrEmpty(json))
|
|
result = null;
|
|
else
|
|
{
|
|
result = JsonSerializer.Deserialize(json, ExifDirectorySourceGenerationContext.Default.ExifDirectory);
|
|
if (result?.FilePath?.Id is null)
|
|
{
|
|
try
|
|
{
|
|
result = Stateless.Methods.IMetadata.GetExifDirectory(filePair.FilePath);
|
|
json = JsonSerializer.Serialize(result, ExifDirectorySourceGenerationContext.Default.ExifDirectory);
|
|
_ = Shared.Models.Stateless.Methods.IPath.WriteAllText(filePair.Match.FullName, json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null);
|
|
}
|
|
catch (Exception) { result = null; }
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public ExifDirectory GetMetadataCollection(FilePath filePath, List<Tuple<string, DateTime>> subFileTuples, List<string> parseExceptions, string[] changesFrom, MappingFromItem mappingFromItem)
|
|
{
|
|
ExifDirectory? result = null;
|
|
List<DateTime> dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList();
|
|
CombinedEnumAndIndex cei = Shared.Models.Stateless.Methods.IPath.GetCombinedEnumAndIndex(_PropertyConfiguration, filePath);
|
|
string fileName = $"{mappingFromItem.FilePath.NameWithoutExtension}{mappingFromItem.FilePath.ExtensionLowered}.json";
|
|
string directory = _ResultSingletonFileGroups[0][cei.Enum][cei.Index];
|
|
FileInfo fileInfo = new(Path.Combine(directory, fileName));
|
|
MoveIf(fileName, cei, directory, fileInfo);
|
|
if (_ForceMetadataLastWriteTimeToCreationTime && !fileInfo.Exists && File.Exists(Path.ChangeExtension(fileInfo.FullName, ".delete")))
|
|
{
|
|
File.Move(Path.ChangeExtension(fileInfo.FullName, ".delete"), fileInfo.FullName);
|
|
fileInfo.Refresh();
|
|
}
|
|
if (_ForceMetadataLastWriteTimeToCreationTime && fileInfo.Exists && fileInfo.LastWriteTime != fileInfo.CreationTime)
|
|
{
|
|
File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime);
|
|
fileInfo.Refresh();
|
|
}
|
|
if (_PropertiesChangedForMetadata)
|
|
result = null;
|
|
else if (!fileInfo.Exists)
|
|
result = null;
|
|
else if (!fileInfo.FullName.EndsWith(".json") && !fileInfo.FullName.EndsWith(".old"))
|
|
throw new ArgumentException("must be a *.json file");
|
|
else if (dateTimes.Count != 0 && dateTimes.Max() > fileInfo.LastWriteTime)
|
|
result = null;
|
|
else
|
|
{
|
|
string json = File.ReadAllText(fileInfo.FullName);
|
|
try
|
|
{
|
|
result = JsonSerializer.Deserialize(json, ExifDirectorySourceGenerationContext.Default.ExifDirectory);
|
|
if (result is null)
|
|
throw new Exception();
|
|
subFileTuples.Add(new Tuple<string, DateTime>(nameof(B_Metadata), fileInfo.LastWriteTime));
|
|
}
|
|
catch (Exception)
|
|
{
|
|
result = null;
|
|
parseExceptions.Add(nameof(B_Metadata));
|
|
}
|
|
}
|
|
if (result is null)
|
|
{
|
|
result = Stateless.Methods.IMetadata.GetExifDirectory(filePath);
|
|
string json = JsonSerializer.Serialize(result, ExifDirectorySourceGenerationContext.Default.ExifDirectory);
|
|
bool updateDateWhenMatches = dateTimes.Count != 0 && fileInfo.Exists && dateTimes.Max() > fileInfo.LastWriteTime;
|
|
DateTime? dateTime = !updateDateWhenMatches ? null : dateTimes.Max();
|
|
if (Shared.Models.Stateless.Methods.IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches, compareBeforeWrite: true, updateToWhenMatches: dateTime))
|
|
{
|
|
if (!_ForceMetadataLastWriteTimeToCreationTime)
|
|
subFileTuples.Add(new Tuple<string, DateTime>(nameof(B_Metadata), DateTime.Now));
|
|
else
|
|
{
|
|
File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime);
|
|
fileInfo.Refresh();
|
|
subFileTuples.Add(new Tuple<string, DateTime>(nameof(B_Metadata), fileInfo.CreationTime));
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static void MoveIf(string fileName, CombinedEnumAndIndex cei, string directory, FileInfo fileInfo)
|
|
{
|
|
string[] segments = directory.Split(cei.Combined);
|
|
string? checkDirectory = segments.Length == 1 ?
|
|
Path.Combine(segments[0], $"{cei.Combined[2..]}") :
|
|
segments.Length == 2 ?
|
|
$"{segments[0]}{cei.Combined[2..]}{segments[1]}" :
|
|
null;
|
|
if (checkDirectory is not null && System.IO.Directory.Exists(checkDirectory))
|
|
{
|
|
string checkFile = Path.Combine(checkDirectory, fileName);
|
|
if (File.Exists(checkFile))
|
|
{
|
|
File.Move(checkFile, fileInfo.FullName);
|
|
fileInfo.Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
(DateTime?, DateTime?[]) IMetadata<MetadataExtractor.Directory>.GetDateTimes(FilePath filePath, IReadOnlyList<MetadataExtractor.Directory> directories)
|
|
{
|
|
List<DateTime?> results = [];
|
|
DateTime? result = null;
|
|
DateTime? dateTime;
|
|
DateTime checkDateTime;
|
|
string dateTimeFormat = IMetaBase.DateTimeFormat();
|
|
MetadataExtractor.Formats.Exif.ExifDirectoryBase? exifDirectoryBase = directories.OfType<MetadataExtractor.Formats.Exif.ExifDirectoryBase>().FirstOrDefault();
|
|
results.Add(new DateTime(filePath.CreationTicks));
|
|
results.Add(new DateTime(filePath.LastWriteTicks));
|
|
if (exifDirectoryBase is not null)
|
|
{
|
|
if (exifDirectoryBase.TryGetDateTime(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTime, out checkDateTime))
|
|
results.Add(checkDateTime);
|
|
else
|
|
{
|
|
dateTime = IMetaBase.GetDateTime(dateTimeFormat, exifDirectoryBase.GetString(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTime));
|
|
if (dateTime is not null)
|
|
results.Add(dateTime.Value);
|
|
}
|
|
if (exifDirectoryBase.TryGetDateTime(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeDigitized, out checkDateTime))
|
|
results.Add(checkDateTime);
|
|
else
|
|
{
|
|
dateTime = IMetaBase.GetDateTime(dateTimeFormat, exifDirectoryBase.GetString(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeDigitized));
|
|
if (dateTime is not null)
|
|
results.Add(dateTime.Value);
|
|
}
|
|
if (exifDirectoryBase.TryGetDateTime(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeOriginal, out checkDateTime))
|
|
{
|
|
result ??= checkDateTime;
|
|
results.Add(checkDateTime);
|
|
}
|
|
else
|
|
{
|
|
dateTime = IMetaBase.GetDateTime(dateTimeFormat, exifDirectoryBase.GetString(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeOriginal));
|
|
if (dateTime is not null)
|
|
{
|
|
result ??= dateTime.Value;
|
|
results.Add(dateTime.Value);
|
|
}
|
|
}
|
|
}
|
|
MetadataExtractor.Formats.Avi.AviDirectory? aviDirectory = directories.OfType<MetadataExtractor.Formats.Avi.AviDirectory>().FirstOrDefault();
|
|
if (aviDirectory is not null)
|
|
{
|
|
if (aviDirectory.TryGetDateTime(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal, out checkDateTime))
|
|
{
|
|
result ??= checkDateTime;
|
|
results.Add(checkDateTime);
|
|
}
|
|
else
|
|
{
|
|
dateTime = IMetaBase.GetDateTime(dateTimeFormat, aviDirectory.GetString(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal));
|
|
if (dateTime is not null)
|
|
{
|
|
result ??= dateTime.Value;
|
|
results.Add(dateTime.Value);
|
|
}
|
|
}
|
|
}
|
|
MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory? quickTimeMovieHeaderDirectory = directories.OfType<MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory>().FirstOrDefault();
|
|
if (quickTimeMovieHeaderDirectory is not null)
|
|
{
|
|
if (quickTimeMovieHeaderDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated, out checkDateTime))
|
|
{
|
|
result ??= checkDateTime;
|
|
results.Add(checkDateTime);
|
|
}
|
|
else
|
|
{
|
|
dateTime = IMetaBase.GetDateTime(dateTimeFormat, quickTimeMovieHeaderDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated));
|
|
if (dateTime is not null)
|
|
{
|
|
result ??= dateTime.Value;
|
|
results.Add(dateTime.Value);
|
|
}
|
|
}
|
|
}
|
|
MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory? quickTimeTrackHeaderDirectory = directories.OfType<MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory>().FirstOrDefault();
|
|
if (quickTimeTrackHeaderDirectory is not null)
|
|
{
|
|
if (quickTimeTrackHeaderDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated, out checkDateTime))
|
|
{
|
|
result ??= checkDateTime;
|
|
results.Add(checkDateTime);
|
|
}
|
|
else
|
|
{
|
|
dateTime = IMetaBase.GetDateTime(dateTimeFormat, quickTimeTrackHeaderDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated));
|
|
if (dateTime is not null)
|
|
{
|
|
result ??= dateTime.Value;
|
|
results.Add(dateTime.Value);
|
|
}
|
|
}
|
|
}
|
|
return new(result, [.. results]);
|
|
}
|
|
|
|
} |