using CliWrap; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Phares.Shared; using ShellProgressBar; using System.Collections.ObjectModel; using System.Text; using View_by_Distance.Metadata.Models; using View_by_Distance.Rename.Models; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Methods; using View_by_Distance.Shared.Models.Stateless.Methods; namespace View_by_Distance.Rename; public class Rename { private record Record(int Index, bool IsIgnoreExtension, bool IsValidImageFormatExtension, List FileHolders, bool FastForwardMovingPictureExpertsGroupFilesPresent, DateTime? DateTimeOriginal, DateTime[] DateTimes, int? Id); private readonly AppSettings _AppSettings; private readonly string _WorkingDirectory; private readonly Configuration _Configuration; private readonly IsEnvironment _IsEnvironment; private readonly IConfigurationRoot _ConfigurationRoot; private readonly Property.Models.Configuration _PropertyConfiguration; public Rename(List args, ILogger? logger, IsEnvironment isEnvironment, IConfigurationRoot configurationRoot, AppSettings appSettings, string workingDirectory, bool isSilent, IConsole console) { if (isSilent) { } if (console is null) throw new NullReferenceException(nameof(console)); _AppSettings = appSettings; _IsEnvironment = isEnvironment; _WorkingDirectory = workingDirectory; _ConfigurationRoot = configurationRoot; Property.Models.Configuration propertyConfiguration = Property.Models.Binder.Configuration.Get(isEnvironment, configurationRoot); Configuration configuration = Models.Binder.Configuration.Get(isEnvironment, configurationRoot, propertyConfiguration); _PropertyConfiguration = propertyConfiguration; _Configuration = configuration; logger?.LogInformation("{RootDirectory}", propertyConfiguration.RootDirectory); Property.Models.Configuration.Verify(propertyConfiguration, requireExist: false); Verify(); List lines = RenameFilesInDirectories(logger); if (lines.Count != 0) { File.WriteAllLines($"D:/Tmp/Phares/{DateTime.Now.Ticks}.tsv", lines); _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(propertyConfiguration.RootDirectory); } } private void Verify() { if (_AppSettings is null) throw new NullReferenceException(nameof(_AppSettings)); if (_IsEnvironment is null) throw new NullReferenceException(nameof(_IsEnvironment)); if (_Configuration is null) throw new NullReferenceException(nameof(_Configuration)); if (_ConfigurationRoot is null) throw new NullReferenceException(nameof(_ConfigurationRoot)); if (_WorkingDirectory is null) throw new NullReferenceException(nameof(_WorkingDirectory)); if (_PropertyConfiguration is null) throw new NullReferenceException(nameof(_PropertyConfiguration)); } private List RenameFilesInDirectories(ILogger? logger) { List results = []; string message; bool nefPresentCheck; bool nefPresent = false; ProgressBar progressBar; List records = []; const string fileSearchFilter = "*"; const bool useCeilingAverage = false; const string directorySearchFilter = "*"; List distinctDirectories = []; B_Metadata metadata = new(_PropertyConfiguration); List<(FileHolder, string, string)> toDoCollection = []; List<(FileHolder, string)> verifiedToDoCollection = []; ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; ReadOnlyCollection filesCollection = IDirectory.GetFilesCollection(_PropertyConfiguration.RootDirectory, directorySearchFilter, fileSearchFilter, useCeilingAverage); int count = filesCollection.Select(l => l.Length).Sum(); foreach (string[] files in filesCollection) { if (files.Length == 0) continue; // foreach (string files[i] in files) // { // if (!files[i].EndsWith(".del")) // continue; // File.Move(files[i], files[i][..^4]); // } // continue; distinctDirectories.Clear(); if (_AppSettings.RenameUndo) { message = $") Undo renaming files for <{files.FirstOrDefault()}>"; progressBar = new(files.Length, message, options); toDoCollection.AddRange(GetRenameUndoToDoCollection(progressBar, files)); } else { message = $"{records.Count:00000}) Gathering records for files next to <{files.FirstOrDefault()}>"; progressBar = new(files.Length, message, options); nefPresentCheck = files.Any(l => l.EndsWith(".NEF")); if (!nefPresentCheck) records.AddRange(GetRecords(metadata, _PropertyConfiguration.Offset + records.Count, progressBar, files)); else { if (!nefPresent) nefPresent = true; records.AddRange(GetRecords(metadata, _PropertyConfiguration.Offset + records.Count, progressBar, (from l in files where l.EndsWith(".JPG") select l).ToArray())); } } progressBar.Dispose(); } if (records.Count != 0) { foreach (Record record in records) { if (record.Id is null) continue; if (_PropertyConfiguration.IntMinValueLength < record.Id.Value.ToString().Length) throw new NotSupportedException(); } message = $"{_PropertyConfiguration.IntMinValueLength}) comparing records"; progressBar = new(records.Count, message, options); toDoCollection.AddRange(GetToDoCollection(progressBar, nefPresent, records)); progressBar.Dispose(); } foreach ((FileHolder fileHolder, string directory, string to) in toDoCollection) { if (distinctDirectories.Contains(directory)) continue; distinctDirectories.Add(directory); } foreach (string distinctDirectory in distinctDirectories) { if (!Directory.Exists(distinctDirectory)) _ = Directory.CreateDirectory(distinctDirectory); } foreach ((FileHolder fileHolder, string directory, string to) in toDoCollection) { if (File.Exists(to)) continue; verifiedToDoCollection.Add(new(fileHolder, to)); File.WriteAllText($"{to}.paddedId", $"{to}{Environment.NewLine}{fileHolder.FullName}"); } ConsoleKey? consoleKey = null; logger?.LogInformation("Ready to Move {verifiedToDoCollection.Count} files[i](s)?", verifiedToDoCollection.Count); for (int y = 0; y < int.MaxValue; y++) { logger?.LogInformation("Press \"Y\" key to move files[i](s), \"N\" key to log files[i](s) or close console to not move files"); consoleKey = System.Console.ReadKey().Key; if (consoleKey is ConsoleKey.Y or ConsoleKey.N) break; } logger?.LogInformation(". . ."); if (consoleKey is null || consoleKey.Value != ConsoleKey.Y) logger?.LogInformation("Nothing moved!"); else { message = ") Renaming files"; progressBar = new(count, message, options); results.AddRange(Move(progressBar, verifiedToDoCollection)); progressBar.Dispose(); logger?.LogInformation("Done Moving"); } return results; } private static List<(FileHolder, string, string)> GetRenameUndoToDoCollection(ProgressBar progressBar, string[] files) { List<(FileHolder, string, string)> results = []; string[] lines; string fileName; string? directory; FileHolder fileHolder; List distinct = []; foreach (string file in files) { progressBar.Tick(); directory = Path.GetDirectoryName(file); if (string.IsNullOrEmpty(directory)) continue; fileName = Path.GetFileName(file); if (!fileName.EndsWith(".paddedId")) continue; lines = File.ReadAllLines(file); if (lines.Length < 2) continue; if (distinct.Contains(lines[1])) continue; distinct.Add(lines[1]); fileHolder = Shared.Models.Stateless.Methods.IFileHolder.Get(lines[0]); results.Add(new(fileHolder, directory, lines[1])); } return results; } private List GetRecords(B_Metadata metadata, int offset, ProgressBar progressBar, string[] files) { List results = []; int? id; string? message; string? directory; FilePath filePath; DateTime[] dateTimes; FileHolder fileHolder; bool isIgnoreExtension; DateTime? dateTimeOriginal; bool isValidImageFormatExtension; ASCIIEncoding asciiEncoding = new(); string[]? fastForwardMovingPictureExpertsGroupFiles; IReadOnlyList directories; for (int i = 0; i < files.Length; i++) { progressBar.Tick(); fileHolder = Shared.Models.Stateless.Methods.IFileHolder.Get(files[i]); if (!fileHolder.Exists) continue; directory = Path.GetDirectoryName(files[i]); if (string.IsNullOrEmpty(directory)) continue; filePath = FilePath.Get(_Configuration.PropertyConfiguration, fileHolder, index: i); if (fileHolder.ExtensionLowered == ".paddedId" || fileHolder.ExtensionLowered == ".lsv" || fileHolder.DirectoryFullPath is null) continue; if (files.Contains($"{fileHolder.FullName}.paddedId")) continue; if (filePath.IsIntelligentIdFormat || filePath.SortOrder is not null) continue; isValidImageFormatExtension = _PropertyConfiguration.ValidImageFormatExtensions.Contains(fileHolder.ExtensionLowered); isIgnoreExtension = isValidImageFormatExtension && _PropertyConfiguration.IgnoreExtensions.Contains(fileHolder.ExtensionLowered); if (!isIgnoreExtension && isValidImageFormatExtension) fastForwardMovingPictureExpertsGroupFiles = null; else { try { directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(files[i]); } catch (Exception) { continue; } CommandTask result = 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", files[i], "-vframes", "1", $"{fileHolder.Name}-%4d.jpg" }) .WithWorkingDirectory(fileHolder.DirectoryFullPath) .ExecuteAsync(); result.Task.Wait(); fastForwardMovingPictureExpertsGroupFiles = Directory.GetFiles(fileHolder.DirectoryFullPath, $"{fileHolder.Name}-*.jpg", SearchOption.TopDirectoryOnly); if (fastForwardMovingPictureExpertsGroupFiles.Length == 0) continue; fileHolder = Shared.Models.Stateless.Methods.IFileHolder.Get(fastForwardMovingPictureExpertsGroupFiles.First()); if (!fileHolder.Name.EndsWith("-0001.jpg")) throw new Exception(); isValidImageFormatExtension = _PropertyConfiguration.ValidImageFormatExtensions.Contains(fileHolder.ExtensionLowered); isIgnoreExtension = isValidImageFormatExtension && _PropertyConfiguration.IgnoreExtensions.Contains(fileHolder.ExtensionLowered); if (isIgnoreExtension || !isValidImageFormatExtension) continue; if (fileHolder.DirectoryFullPath is null) continue; } (dateTimeOriginal, dateTimes, id, message) = Property.Models.Stateless.IProperty.Get(_PropertyConfiguration, _PropertyConfiguration.PopulatePropertyId, metadata, fileHolder, isIgnoreExtension, isValidImageFormatExtension, asciiEncoding); if (fastForwardMovingPictureExpertsGroupFiles is not null) { fileHolder = Shared.Models.Stateless.Methods.IFileHolder.Get(files[i]); foreach (string fastForwardMovingPictureExpertsGroupFile in fastForwardMovingPictureExpertsGroupFiles) File.Delete(fastForwardMovingPictureExpertsGroupFile); } if (message is not null) throw new Exception(message); results.Add(new(i + offset, isIgnoreExtension, isValidImageFormatExtension, [fileHolder], fastForwardMovingPictureExpertsGroupFiles is null, dateTimeOriginal, dateTimes, id)); } return results; } private List<(FileHolder, string, string)> GetToDoCollection(ProgressBar progressBar, bool nefPresent, List records) { List<(FileHolder, string, string)> results = []; int season; string paddedId; string checkFile; bool? isWrongYear; DateTime dateTime; string seasonName; TimeSpan? timeSpan; string directoryName; FileHolder fileHolder; string? seasonDirectory; const string jpg = ".jpg"; DateTime? minimumDateTime; string checkFileExtension; DateTime? dateTimeFromName; const string jpeg = ".jpeg"; List distinct = []; string[] directoryNameSegments; DateTime? dateTimeOriginalByLogic; foreach (Record record in records) { progressBar.Tick(); if (record.FileHolders.Count != 1) continue; fileHolder = record.FileHolders.First(); if (!fileHolder.Exists) continue; if (string.IsNullOrEmpty(fileHolder.DirectoryFullPath)) continue; dateTimeFromName = record.DateTimes.Length == 0 ? null : record.DateTimes.First(); if (fileHolder.ExtensionLowered == jpeg) { if (!record.IsIgnoreExtension && record.IsValidImageFormatExtension) { if (File.Exists($"{fileHolder.FullName}.paddedId")) { checkFile = Path.Combine(fileHolder.DirectoryFullPath, $"{fileHolder.NameWithoutExtension}{jpg}.paddedId"); if (File.Exists(checkFile)) continue; if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); results.Add(new(Shared.Models.Stateless.Methods.IFileHolder.Get($"{fileHolder.FullName}.paddedId"), fileHolder.DirectoryFullPath, checkFile)); } checkFile = Path.Combine(fileHolder.DirectoryFullPath, $"{fileHolder.NameWithoutExtension}{jpg}"); if (File.Exists(checkFile)) continue; if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); results.Add(new(fileHolder, fileHolder.DirectoryFullPath, checkFile)); if (nefPresent) results.Add(new(Shared.Models.Stateless.Methods.IFileHolder.Get($"{fileHolder.FullName[..^4]}.tif"), fileHolder.DirectoryFullPath, $"{checkFile[..^4]}.tif")); if (nefPresent) results.Add(new(Shared.Models.Stateless.Methods.IFileHolder.Get($"{fileHolder.FullName[..^4]}.nef"), fileHolder.DirectoryFullPath, $"{checkFile[..^4]}.nef")); if (File.Exists(checkFile)) continue; File.Move(fileHolder.FullName, checkFile); fileHolder = Shared.Models.Stateless.Methods.IFileHolder.Get(checkFile); if (fileHolder.DirectoryFullPath is null) continue; } } minimumDateTime = record.DateTimes.Min(); if (minimumDateTime is null) throw new NotSupportedException(); if (record.DateTimeOriginal is null) timeSpan = null; else timeSpan = new(Math.Abs(minimumDateTime.Value.Ticks - record.DateTimeOriginal.Value.Ticks)); if (timeSpan is null || timeSpan.Value.TotalMinutes > _AppSettings.MaxMinutesDelta) timeSpan = null; else dateTimeOriginalByLogic = record.DateTimeOriginal; if (timeSpan is null || timeSpan.Value.TotalMinutes > _AppSettings.MaxMinutesDelta) { if (string.IsNullOrEmpty(_AppSettings.DefaultUnknownDirectoryName)) (isWrongYear, seasonDirectory) = (null, !_AppSettings.ForceIdName ? null : fileHolder.DirectoryFullPath); else (isWrongYear, seasonDirectory) = (null, !_AppSettings.ForceIdName ? null : Path.Combine(fileHolder.DirectoryFullPath, _AppSettings.DefaultUnknownDirectoryName)); } else { directoryName = Path.GetFileName(fileHolder.DirectoryFullPath); directoryNameSegments = directoryName.Split(' '); if (dateTimeFromName is null) isWrongYear = null; else (isWrongYear, _) = Shared.Models.Stateless.Methods.IProperty.IsWrongYear(directoryNameSegments, dateTimeFromName.Value.ToString("yyyy")); dateTime = minimumDateTime.Value.AddTicks(timeSpan.Value.Ticks); (season, seasonName) = Shared.Models.Stateless.Methods.IProperty.GetSeason(dateTime.DayOfYear); seasonDirectory = Path.Combine(fileHolder.DirectoryFullPath, $"{dateTime.Year}.{season} {seasonName}"); } if (seasonDirectory is null || (isWrongYear is not null && isWrongYear.Value)) { if (dateTimeFromName is not null && isWrongYear is not null && isWrongYear.Value) minimumDateTime = dateTimeFromName.Value; else if (record.DateTimeOriginal is not null) minimumDateTime = record.DateTimeOriginal.Value; if (minimumDateTime is null) continue; checkFileExtension = fileHolder.ExtensionLowered == jpeg ? jpg : fileHolder.ExtensionLowered; checkFile = Path.Combine(fileHolder.DirectoryFullPath, $"{minimumDateTime.Value:yyyy-MM-dd}.{minimumDateTime.Value.Ticks}.{fileHolder.Length}{checkFileExtension}"); if (checkFile == fileHolder.FullName) continue; if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); results.Add(new(fileHolder, fileHolder.DirectoryFullPath, checkFile)); if (nefPresent) results.Add(new(Shared.Models.Stateless.Methods.IFileHolder.Get($"{fileHolder.FullName[..^4]}.tif"), fileHolder.DirectoryFullPath, $"{checkFile[..^4]}.tif")); if (nefPresent) results.Add(new(Shared.Models.Stateless.Methods.IFileHolder.Get($"{fileHolder.FullName[..^4]}.nef"), fileHolder.DirectoryFullPath, $"{checkFile[..^4]}.nef")); } else { if (record.Id is null) continue; paddedId = IId.GetPaddedId(_PropertyConfiguration, record.Id.Value, hasIgnoreKeyword: null, hasDateTimeOriginal: null, record.Index); checkFileExtension = fileHolder.ExtensionLowered == jpeg ? jpg : fileHolder.ExtensionLowered; checkFile = Path.Combine(seasonDirectory, $"{paddedId}{checkFileExtension}"); if (checkFile == fileHolder.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(fileHolder, seasonDirectory, checkFile)); if (nefPresent) results.Add(new(Shared.Models.Stateless.Methods.IFileHolder.Get($"{fileHolder.FullName[..^4]}.tif"), seasonDirectory, $"{checkFile[..^4]}.tif")); if (nefPresent) results.Add(new(Shared.Models.Stateless.Methods.IFileHolder.Get($"{fileHolder.FullName[..^4]}.nef"), seasonDirectory, $"{checkFile[..^4]}.nef")); } } return results; } private static List Move(ProgressBar progressBar, List<(FileHolder, string)> verifiedToDoCollection) { List results = []; foreach ((FileHolder fileHolder, string to) in verifiedToDoCollection) { progressBar.Tick(); results.Add(fileHolder.NameWithoutExtension); try { File.Move(fileHolder.FullName, to); } catch (Exception) { } } return results; } }