ImgListCmd.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. using qdr.app.tools.qfu.Commands.Aspects;
  2. using Quadarax.Foundation.Core.QConsole;
  3. using Quadarax.Foundation.Core.QConsole.Argument;
  4. using Quadarax.Foundation.Core.QConsole.Attributes;
  5. using Quadarax.Foundation.Core.Value;
  6. using System.Drawing;
  7. using System.Drawing.Imaging;
  8. namespace qdr.app.tools.qfu.Commands
  9. {
  10. [CommandDefinition]
  11. internal class ImgListCmd : BaseCmd
  12. {
  13. #region *** Constants ***
  14. private const string CMD_NAME = "img_list";
  15. private const string CMD_DESCR = "List all image files from source directory with dimensions, resolution and size information.";
  16. // Common image file extensions
  17. private static readonly string[] ImageExtensions = {
  18. ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".tif",
  19. ".webp", ".ico", ".svg", ".psd", ".raw", ".cr2", ".nef", ".arw"
  20. };
  21. #endregion
  22. #region *** Properties ***
  23. public override string Name => CMD_NAME;
  24. public override string Description => CMD_DESCR;
  25. protected string Directory { get; private set; } = string.Empty;
  26. protected bool IncludeSubdirectories { get; private set; } = false;
  27. protected string[] ExcludePatterns { get; private set; } = Array.Empty<string>();
  28. #endregion
  29. #region *** Constructors ***
  30. public ImgListCmd(Engine engine) : base(engine)
  31. {
  32. }
  33. #endregion
  34. #region *** Overrides ***
  35. #region **** EXECUTE ****
  36. protected override Result OnExecute()
  37. {
  38. var subdirText = IncludeSubdirectories ? " and subdirectories" : "";
  39. var excludeText = ExcludePatterns.Length > 0 ? $" (excluding: {string.Join(", ", ExcludePatterns)})" : "";
  40. WriteCaption($"Listing image files from '{Directory}'{subdirText}{excludeText}...");
  41. WriteInfo($"{"Dimensions",-12}\t{"Resolution",-15}\t{"Size",-10}\t{"File Path"}");
  42. WriteInfo($"{new string('-', 12)}\t{new string('-', 15)}\t{new string('-', 10)}\t{new string('-', 50)}");
  43. var totalFiles = 0;
  44. var processedFiles = 0;
  45. var errors = new List<string>();
  46. try
  47. {
  48. var searchOption = IncludeSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
  49. var allImageFiles = new List<string>();
  50. // Get all files with image extensions
  51. foreach (var extension in ImageExtensions)
  52. {
  53. try
  54. {
  55. var files = FileSystem.Directory.GetFiles(Directory, $"*{extension}", searchOption);
  56. allImageFiles.AddRange(files);
  57. }
  58. catch (Exception ex)
  59. {
  60. WriteWarning($"Failed to search for {extension} files: {ex.Message}");
  61. }
  62. }
  63. // Remove duplicates and apply exclusion filters
  64. var uniqueFiles = allImageFiles.Distinct().ToList();
  65. var filteredFiles = ApplyExcludeFilters(uniqueFiles);
  66. totalFiles = filteredFiles.Count;
  67. foreach (var filePath in filteredFiles.OrderBy(f => f))
  68. {
  69. processedFiles++;
  70. try
  71. {
  72. var relativePath = Path.GetRelativePath(Directory, filePath);
  73. var imageInfo = GetImageInfo(filePath);
  74. if (imageInfo != null)
  75. {
  76. WriteInfo($"{imageInfo.Dimensions,-12}\t{imageInfo.Resolution,-15}\t{imageInfo.Size,-10}\t{relativePath}");
  77. }
  78. else
  79. {
  80. WriteWarning($"{"N/A",-12}\t{"N/A",-15}\t{GetFileSize(filePath),-10}\t{relativePath} (unsupported format)");
  81. }
  82. }
  83. catch (Exception ex)
  84. {
  85. var relativePath = Path.GetRelativePath(Directory, filePath);
  86. var errorMsg = $"Failed to process '{relativePath}': {ex.Message}";
  87. errors.Add(errorMsg);
  88. WriteError($"{"ERROR",-12}\t{"ERROR",-15}\t{GetFileSize(filePath),-10}\t{relativePath}");
  89. }
  90. }
  91. WriteCaption($"Successfully processed: {processedFiles} of {totalFiles} image files.");
  92. if (errors.Count > 0)
  93. {
  94. WriteWarning($"Encountered {errors.Count} errors during operation.");
  95. }
  96. return errors.Count == 0 ? new Result() : new Result(new Exception($"Failed to process {errors.Count} files."));
  97. }
  98. catch (Exception ex)
  99. {
  100. WriteError($"Critical error during operation: {ex.Message}");
  101. return new Result(ex);
  102. }
  103. }
  104. #endregion
  105. #region **** Arguments ****
  106. protected override void OnValidateArguments()
  107. {
  108. base.OnValidateArguments();
  109. // Get and validate directory (using Source from BaseCmd)
  110. Directory = Source;
  111. if (!FileSystem.Directory.Exists(Directory))
  112. throw new ArgumentException($"Directory '{Directory}' does not exist.", SourceAspect.ARG_SRC_NAME);
  113. // Get flags
  114. IncludeSubdirectories = GetArgumentValueOrDefault<bool>(SubdirsAspect.ARG_SUBDIRS_NAME, false);
  115. // Get exclude patterns
  116. ExcludePatterns = ExcludeAspect.GetExcludePatterns(this);
  117. }
  118. protected override IEnumerable<AbstractArgument> OnSetupArguments()
  119. {
  120. var list = base.OnSetupArguments().ToList();
  121. // Add our specific arguments
  122. list.AddRange(SubdirsAspect.SetupArguments());
  123. list.AddRange(ExcludeAspect.SetupArguments());
  124. return list;
  125. }
  126. #endregion
  127. #region **** Private Operations ****
  128. private ImageInfo? GetImageInfo(string filePath)
  129. {
  130. try
  131. {
  132. using var fileStream = FileSystem.File.OpenRead(filePath);
  133. using var image = Image.FromStream(fileStream, false, false);
  134. var width = image.Width;
  135. var height = image.Height;
  136. var dimensions = $"{width}x{height}";
  137. // Get resolution (DPI)
  138. var horizontalRes = image.HorizontalResolution;
  139. var verticalRes = image.VerticalResolution;
  140. var resolution = $"{horizontalRes:F0}x{verticalRes:F0} DPI";
  141. // Get file size
  142. var size = GetFileSize(filePath);
  143. return new ImageInfo(dimensions, resolution, size);
  144. }
  145. catch
  146. {
  147. // If System.Drawing can't handle the image, try to at least get basic info
  148. return null;
  149. }
  150. }
  151. private string GetFileSize(string filePath)
  152. {
  153. try
  154. {
  155. var fileInfo = FileSystem.FileInfo.New(filePath);
  156. var bytes = fileInfo.Length;
  157. return bytes switch
  158. {
  159. < 1024 => $"{bytes} B",
  160. < 1024 * 1024 => $"{bytes / 1024.0:F1} KB",
  161. < 1024 * 1024 * 1024 => $"{bytes / (1024.0 * 1024.0):F1} MB",
  162. _ => $"{bytes / (1024.0 * 1024.0 * 1024.0):F1} GB"
  163. };
  164. }
  165. catch
  166. {
  167. return "N/A";
  168. }
  169. }
  170. private List<string> ApplyExcludeFilters(List<string> files)
  171. {
  172. if (ExcludePatterns.Length == 0)
  173. return files;
  174. var filteredFiles = new List<string>();
  175. foreach (var file in files)
  176. {
  177. var fileName = Path.GetFileName(file);
  178. var shouldExclude = false;
  179. foreach (var pattern in ExcludePatterns)
  180. {
  181. if (IsMatch(fileName, pattern) || IsMatch(file, pattern))
  182. {
  183. shouldExclude = true;
  184. break;
  185. }
  186. }
  187. if (!shouldExclude)
  188. {
  189. filteredFiles.Add(file);
  190. }
  191. }
  192. return filteredFiles;
  193. }
  194. private bool IsMatch(string input, string pattern)
  195. {
  196. // Simple pattern matching - supports * wildcards
  197. if (string.IsNullOrEmpty(pattern))
  198. return false;
  199. if (pattern == "*")
  200. return true;
  201. // Convert pattern to regex-like matching
  202. var regexPattern = "^" + pattern.Replace("*", ".*").Replace("?", ".") + "$";
  203. return System.Text.RegularExpressions.Regex.IsMatch(input, regexPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
  204. }
  205. #endregion
  206. #endregion
  207. #region *** Helper Classes ***
  208. private record ImageInfo(string Dimensions, string Resolution, string Size);
  209. #endregion
  210. }
  211. }