{"version":3,"sources":["../src/cli.ts","../src/lib.ts","../src/utils.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { parseArgs } from \"node:util\";\nimport { resolve } from \"node:path\";\nimport { access, unlink } from \"node:fs/promises\";\nimport { createInterface } from \"node:readline\";\nimport findDuplicateImages from \"./lib\";\n\ninterface CLIOptions {\n  source: string[];\n  hashSize: number;\n  maxDuplicates: number;\n  maxDistance: number;\n  delete: boolean;\n  forceDelete: boolean;\n  help: boolean;\n  version: boolean;\n}\n\nconst HELP_TEXT = `\nDuplicate Image Finder\n\nUsage: img-duplicates [options] <source...>\n\nArguments:\n  source                One or more directories or image files to search for duplicates\n\nOptions:\n  -h, --hash-size <size>     Hash size for perceptual hashing (default: 8)\n  -d, --max-duplicates <num> Maximum number of duplicates to find per image (default: 100)\n  -m, --max-distance <dist>  Maximum distance for similarity matching (default: 5)\n  --delete                   Delete duplicate images (keeps highest resolution, asks for confirmation)\n  --force-delete             Delete duplicate images without confirmation\n  --help                     Show this help message\n  --version                  Show version number\n\nExamples:\n  img-duplicates /path/to/images\n  img-duplicates /path/to/dir1 /path/to/dir2\n  img-duplicates --hash-size 16 --max-distance 3 /path/to/images\n  img-duplicates /path/to/image1.jpg /path/to/image2.png /path/to/dir\n  img-duplicates --delete /path/to/images\n  img-duplicates --force-delete /path/to/images\n\nNote: When using --delete or --force-delete, the image with the highest resolution\n      in each duplicate group will be kept, and all others will be deleted.\n`;\n\nasync function parseArguments(): Promise<CLIOptions> {\n  try {\n    const { values, positionals } = parseArgs({\n      args: process.argv.slice(2),\n      options: {\n        \"hash-size\": {\n          type: \"string\",\n          short: \"h\",\n        },\n        \"max-duplicates\": {\n          type: \"string\",\n          short: \"d\",\n        },\n        \"max-distance\": {\n          type: \"string\",\n          short: \"m\",\n        },\n        delete: {\n          type: \"boolean\",\n        },\n        \"force-delete\": {\n          type: \"boolean\",\n        },\n        help: {\n          type: \"boolean\",\n        },\n        version: {\n          type: \"boolean\",\n        },\n      },\n      allowPositionals: true,\n    });\n\n    return {\n      source: positionals,\n      hashSize: values[\"hash-size\"] ? parseInt(values[\"hash-size\"]) : 8,\n      maxDuplicates: values[\"max-duplicates\"]\n        ? parseInt(values[\"max-duplicates\"])\n        : 100,\n      maxDistance: values[\"max-distance\"]\n        ? parseInt(values[\"max-distance\"])\n        : 5,\n      delete: values.delete || false,\n      forceDelete: values[\"force-delete\"] || false,\n      help: values.help || false,\n      version: values.version || false,\n    };\n  } catch (error) {\n    console.error(\"Error parsing arguments:\", error.message);\n    process.exit(1);\n  }\n}\n\nasync function validatePaths(paths: string[]): Promise<string[]> {\n  const resolvedPaths: string[] = [];\n\n  for (const path of paths) {\n    const resolvedPath = resolve(path);\n    try {\n      await access(resolvedPath);\n      resolvedPaths.push(resolvedPath);\n    } catch (error) {\n      console.error(`Error: Path does not exist: ${path}`);\n      process.exit(1);\n    }\n  }\n\n  return resolvedPaths;\n}\n\nfunction validateOptions(options: CLIOptions): void {\n  if (options.hashSize < 1 || options.hashSize > 32) {\n    console.error(\"Error: Hash size must be between 1 and 32\");\n    process.exit(1);\n  }\n\n  if (options.maxDuplicates < 1) {\n    console.error(\"Error: Max duplicates must be at least 1\");\n    process.exit(1);\n  }\n\n  if (options.maxDistance < 0) {\n    console.error(\"Error: Max distance must be non-negative\");\n    process.exit(1);\n  }\n\n  if (options.delete && options.forceDelete) {\n    console.error(\n      \"Error: Cannot use both --delete and --force-delete at the same time\"\n    );\n    process.exit(1);\n  }\n}\n\nfunction formatResults(\n  duplicates: Array<Array<{ path: string; width: number; height: number }>>,\n  showDelete: boolean = false\n): void {\n  if (duplicates.length === 0) {\n    console.log(\"No duplicate images found.\");\n    return;\n  }\n\n  console.log(`Found ${duplicates.length} group(s) of duplicate images:\\n`);\n\n  duplicates.forEach((group, index) => {\n    console.log(`Group ${index + 1}:`);\n    group.forEach((image, imageIndex) => {\n      const resolution = `${image.width}x${image.height}`;\n      let marker = \"\";\n      if (imageIndex === 0) {\n        marker = showDelete\n          ? \"(highest resolution - will be kept)\"\n          : \"(highest resolution)\";\n      } else if (showDelete) {\n        marker = \"(will be deleted)\";\n      }\n      console.log(`  ${image.path} [${resolution}] ${marker}`);\n    });\n    console.log(\"\");\n  });\n}\n\nasync function askForConfirmation(message: string): Promise<boolean> {\n  const rl = createInterface({\n    input: process.stdin,\n    output: process.stdout,\n  });\n\n  return new Promise((resolve) => {\n    rl.question(`${message} (y/N): `, (answer) => {\n      rl.close();\n      resolve(answer.toLowerCase() === \"y\" || answer.toLowerCase() === \"yes\");\n    });\n  });\n}\n\nasync function deleteDuplicates(\n  duplicates: Array<Array<{ path: string; width: number; height: number }>>,\n  forceDelete: boolean = false\n): Promise<void> {\n  if (duplicates.length === 0) {\n    return;\n  }\n\n  const filesToDelete: string[] = [];\n\n  // Collect all files to delete (skip the first one in each group as it has the highest resolution)\n  duplicates.forEach((group) => {\n    for (let i = 1; i < group.length; i++) {\n      filesToDelete.push(group[i].path);\n    }\n  });\n\n  if (filesToDelete.length === 0) {\n    console.log(\"No files to delete.\");\n    return;\n  }\n\n  console.log(`\\nAbout to delete ${filesToDelete.length} duplicate image(s):`);\n  filesToDelete.forEach((file) => console.log(`  ${file}`));\n  console.log(\"\");\n\n  let shouldDelete = forceDelete;\n  if (!forceDelete) {\n    shouldDelete = await askForConfirmation(\n      \"Do you want to proceed with deletion?\"\n    );\n  }\n\n  if (!shouldDelete) {\n    console.log(\"Deletion cancelled.\");\n    return;\n  }\n\n  let deletedCount = 0;\n  let errorCount = 0;\n\n  for (const file of filesToDelete) {\n    try {\n      await unlink(file);\n      console.log(`Deleted: ${file}`);\n      deletedCount++;\n    } catch (error) {\n      console.error(`Failed to delete ${file}: ${error.message}`);\n      errorCount++;\n    }\n  }\n\n  console.log(\n    `\\nDeletion completed: ${deletedCount} files deleted, ${errorCount} errors.`\n  );\n}\n\nasync function main(): Promise<void> {\n  const options = await parseArguments();\n\n  if (options.help) {\n    console.log(HELP_TEXT);\n    process.exit(0);\n  }\n\n  if (options.version) {\n    console.log(\"1.0.2\");\n    process.exit(0);\n  }\n\n  if (options.source.length === 0) {\n    console.error(\"Error: No source paths provided\");\n    console.log(HELP_TEXT);\n    process.exit(1);\n  }\n\n  validateOptions(options);\n  const validatedPaths = await validatePaths(options.source);\n\n  try {\n    console.log(\"Searching for duplicate images...\");\n\n    const duplicates = await findDuplicateImages(validatedPaths, {\n      hashSize: options.hashSize,\n      maxDuplicates: options.maxDuplicates,\n      maxDistance: options.maxDistance,\n    });\n\n    const shouldDelete = options.delete || options.forceDelete;\n    formatResults(duplicates, shouldDelete);\n\n    if (shouldDelete) {\n      await deleteDuplicates(duplicates, options.forceDelete);\n    }\n  } catch (error) {\n    console.error(\"Error finding duplicates:\", error.message);\n    process.exit(1);\n  }\n}\n\n// Run the CLI\nmain().catch((error) => {\n  console.error(\"Unexpected error:\", error);\n  process.exit(1);\n});\n","import path from \"node:path\";\nimport sharp from \"sharp\";\nimport fs from \"node:fs/promises\";\nimport createKDTree from \"static-kdtree\";\nimport { dhash, isFolder, isImageFile, mapLimit, pathExists } from \"./utils\";\n\n/**\n * Configuration options for the duplicate image detection algorithm.\n */\ninterface Options {\n  /**\n   * Size of the hash grid used for perceptual hashing.\n   * Larger values provide more precision but require more processing time.\n   * @default 8\n   */\n  hashSize?: number;\n\n  /**\n   * Maximum number of duplicate images to find for each source image.\n   * @default 100\n   */\n  maxDuplicates?: number;\n\n  /**\n   * Maximum Hamming distance between image hashes to consider them as duplicates.\n   * Lower values require higher visual similarity.\n   * @default 5\n   */\n  maxDistance?: number;\n}\n\nconst defaultOptions = {\n  hashSize: 8,\n  maxDuplicates: 100,\n  maxDistance: 5,\n};\n\n/**\n * Finds duplicate images based on visual similarity using perceptual hashing.\n *\n * This function analyzes images using difference hashing (dHash) to create perceptual fingerprints\n * and then uses a k-d tree for efficient similarity matching. Images are considered duplicates\n * if their hash distance is within the specified threshold.\n *\n * @param source - Path to a directory containing images, or an array of paths to directories and/or image files\n * @param options - Configuration options for the duplicate detection algorithm\n * @param options.hashSize - Size of the hash grid (default: 8). Larger values provide more precision but slower processing\n * @param options.maxDuplicates - Maximum number of duplicates to find per image (default: 100)\n * @param options.maxDistance - Maximum Hamming distance between hashes to consider images as duplicates (default: 5). Lower values require higher similarity\n *\n * @returns Promise that resolves to an array of duplicate groups. Each group is an array of image objects\n *          containing path, width, and height. Images within each group are sorted by resolution (highest first).\n *\n * @throws {Error} When image metadata cannot be read or when source paths don't exist\n *\n * @example\n * ```typescript\n * // Find duplicates in a single directory\n * const duplicates = await findDuplicateImages('/path/to/images');\n *\n * // Find duplicates across multiple sources with custom settings\n * const duplicates = await findDuplicateImages(\n *   ['/path/to/dir1', '/path/to/dir2', '/path/to/image.jpg'],\n *   {\n *     hashSize: 16,     // Higher precision\n *     maxDistance: 3,   // Stricter similarity\n *     maxDuplicates: 5  // Limit results\n *   }\n * );\n *\n * // Process results\n * duplicates.forEach((group, index) => {\n *   console.log(`Duplicate group ${index + 1}:`);\n *   group.forEach((image, i) => {\n *     const isHighest = i === 0 ? '(highest resolution)' : '';\n *     console.log(`  ${image.path} [${image.width}x${image.height}] ${isHighest}`);\n *   });\n * });\n * ```\n *\n * @since 1.0.0\n */\nexport default async function findDuplicateImages(\n  source: string | string[],\n  {\n    hashSize = 8,\n    maxDuplicates = 100,\n    maxDistance = 5,\n  }: Options = defaultOptions\n): Promise<Array<Array<{ path: string; width: number; height: number }>>> {\n  let imageFilePaths: string[];\n\n  if (Array.isArray(source)) {\n    // If source is an array of file and directory paths, process each item\n    const allImagePaths: string[] = [];\n\n    for (const item of source) {\n      // Check if the path exists first\n      if (!(await pathExists(item))) {\n        continue;\n      }\n\n      if (await isFolder(item)) {\n        // If it's a directory, read files and add image paths\n        try {\n          const files = await fs.readdir(item);\n          const imageFiles = files.filter(isImageFile).sort(); // Sort for consistent ordering\n          const imagePaths = imageFiles.map((f) => path.join(item, f));\n          allImagePaths.push(...imagePaths);\n        } catch {\n          // Skip directories that can't be read\n          continue;\n        }\n      } else if (isImageFile(item)) {\n        // If it's an image file, add it directly\n        allImagePaths.push(item);\n      }\n    }\n\n    imageFilePaths = allImagePaths.sort(); // Sort for consistent ordering\n  } else {\n    // If source is a directory path, read files and create full paths\n    const files = await fs.readdir(source);\n    const imageFiles = files.filter(isImageFile).sort(); // Sort for consistent ordering\n    imageFilePaths = imageFiles.map((f) => path.join(source, f));\n  }\n  const hashList: Array<Array<number>> = await mapLimit(\n    imageFilePaths,\n    1,\n    async (file: string): Promise<Array<number>> => {\n      const hash = await dhash(file, hashSize);\n      return [...hash];\n    }\n  );\n\n  const tree = createKDTree(hashList);\n\n  const duplicateIdxSet = new Set();\n  const duplicates: Array<Array<string>> = [];\n\n  for (let i = 0; i < hashList.length; i++) {\n    if (duplicateIdxSet.has(i)) {\n      continue;\n    }\n\n    const hash = hashList[i];\n    let duplicatesIdx = tree.knn(hash, maxDuplicates + 1, maxDistance); // +1 to account for the original image\n\n    if (duplicatesIdx && duplicatesIdx.length > 1) {\n      // Filter out any indices that have already been processed\n      duplicatesIdx = duplicatesIdx.filter((idx) => !duplicateIdxSet.has(idx));\n\n      // Only add if we still have more than one duplicate after filtering\n      if (duplicatesIdx.length > 1) {\n        // Ensure we don't exceed the maxDuplicates limit\n        const limitedDuplicates = duplicatesIdx.slice(0, maxDuplicates);\n        const dups = limitedDuplicates.map((i) => imageFilePaths[i]);\n        duplicates.push(dups);\n\n        // Mark all found duplicates as processed\n        for (const idx of limitedDuplicates) {\n          duplicateIdxSet.add(idx);\n        }\n      }\n    }\n  }\n\n  // Get metadata for each duplicate group and sort by resolution\n  const duplicatesWithMetadata: Array<\n    Array<{ path: string; width: number; height: number }>\n  > = [];\n\n  for (const files of duplicates) {\n    const filesWithMetadata: Array<{\n      path: string;\n      width: number;\n      height: number;\n    }> = [];\n\n    for (const file of files) {\n      if (await pathExists(file)) {\n        try {\n          const metadata = await sharp(file).metadata();\n          filesWithMetadata.push({\n            path: file,\n            width: metadata.width || 0,\n            height: metadata.height || 0,\n          });\n        } catch (error) {\n          throw new Error(`Could not read metadata for ${file}:`, error);\n        }\n      }\n    }\n\n    // Sort by resolution (width * height) in descending order (highest resolution first)\n    filesWithMetadata.sort((a, b) => {\n      // First sort by resolution (highest first)\n      const resolutionDiff = b.width * b.height - a.width * a.height;\n      if (resolutionDiff !== 0) return resolutionDiff;\n\n      // If resolution is the same, sort by filename\n      return path.basename(a.path).localeCompare(path.basename(b.path));\n    });\n\n    if (filesWithMetadata.length > 0) {\n      duplicatesWithMetadata.push(filesWithMetadata);\n    }\n  }\n\n  return duplicatesWithMetadata;\n}\n","import sharp from \"sharp\";\nimport fs from \"node:fs/promises\";\nimport assert from \"assert\";\n\n/**\n * Checks if a given path points to a directory.\n * @param path - The file system path to check\n * @returns Promise that resolves to true if the path is a directory\n */\nexport async function isFolder(path: string) {\n  try {\n    return (await fs.stat(path)).isDirectory();\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Processes an array with a concurrency limit to avoid overwhelming the system.\n * @param array - Array of items to process\n * @param limit - Maximum number of concurrent operations\n * @param iteratee - Async function to apply to each item\n * @returns Promise that resolves to an array of results\n */\nexport async function mapLimit<T, R>(\n  array: T[],\n  limit: number,\n  iteratee: (item: T) => Promise<R>\n): Promise<R[]> {\n  const results: R[] = [];\n  for (let i = 0; i < array.length; i += limit) {\n    const batch = array.slice(i, i + limit);\n    const batchResults = await Promise.all(batch.map(iteratee));\n    results.push(...batchResults);\n  }\n  return results;\n}\n\n/**\n * Checks if a file exists at the given path.\n * @param filePath - The path to check\n * @returns Promise that resolves to true if the file exists\n */\nexport async function pathExists(filePath: string): Promise<boolean> {\n  try {\n    await fs.access(filePath);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Determines if a file is a supported image format based on its extension.\n * @param path - The file path to check\n * @returns True if the file extension indicates a supported image format\n */\nexport function isImageFile(path: string) {\n  return (\n    path.endsWith(\".png\") ||\n    path.endsWith(\".jpg\") ||\n    path.endsWith(\".jpeg\") ||\n    path.endsWith(\".webp\") ||\n    path.endsWith(\".gif\") ||\n    path.endsWith(\".avif\") ||\n    path.endsWith(\".tiff\") ||\n    path.endsWith(\".tif\") ||\n    path.endsWith(\".svg\")\n  );\n}\n\nexport function px(pixels: Buffer, width: number, x: number, y: number) {\n  const pixel = width * y + x;\n  assert(pixel < pixels.length);\n  return pixels[pixel];\n}\n\nexport function binaryToHex(s: string) {\n  let output = \"\";\n  for (let i = 0; i < s.length; i += 4) {\n    const bytes = s.slice(i, i + 4);\n    const decimal = parseInt(bytes, 2);\n    const hex = decimal.toString(16);\n    output += hex;\n  }\n  return Buffer.from(output, \"hex\");\n}\n\n/**\n * Computes a difference hash (dHash) for an image, creating a perceptual fingerprint.\n *\n * The dHash algorithm works by:\n * 1. Converting the image to grayscale\n * 2. Resizing to a small grid (hashSize x hashSize+1)\n * 3. Comparing adjacent pixels to create a binary hash\n * 4. Converting the binary string to a hexadecimal buffer\n *\n * This creates a hash that is resilient to minor changes like compression,\n * resizing, and slight color adjustments while being sensitive to structural changes.\n *\n * @param path - Path to the image file\n * @param hashSize - Size of the hash grid (default: 8, creates 64-bit hash)\n * @returns Promise that resolves to a Buffer containing the image hash\n * @throws {Error} When the image cannot be processed by Sharp\n */\nexport async function dhash(path: string, hashSize = 8) {\n  const height = hashSize;\n  const width = height + 1;\n\n  // Covert to small gray image\n  const pixels = await sharp(path)\n    .grayscale()\n    .resize({ width, height, fit: \"fill\" })\n    .raw()\n    .toBuffer();\n\n  let difference = \"\";\n  for (let row = 0; row < height; row++) {\n    for (let col = 0; col < height; col++) {\n      // height is not a mistake here...\n      const left = px(pixels, width, col, row);\n      const right = px(pixels, width, col + 1, row);\n      difference += left < right ? 1 : 0;\n    }\n  }\n  return binaryToHex(difference);\n}"],"mappings":"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","names":["sharp","fs","path","fs","i","sharp","path","resolve"]}