CLI Reference
dublette <DIRECTORY>... [OPTIONS]One or more directories are required. Dublette scans all of them recursively and detects duplicates across directories as well as within them.
dublette ~/Photosdublette 2020 2021 2022dublette ~/Photos ~/Backup/PhotosShell glob expansion works naturally — dublette 202* expands to all matching directories
before dublette runs, so no special pattern syntax is needed.
Options
Section titled “Options”| Flag | Long | Type | Default | Description |
|---|---|---|---|---|
-t |
--threshold |
integer | 1 |
Maximum hamming distance to consider two image/video files as duplicates |
-n |
--dry-run |
flag | false |
List duplicates without deleting any files |
--only |
images, videos, or audio |
all | Restrict processing to one media type | |
--audio-match |
recording or encoding |
recording |
How audio files are matched | |
--audio-threshold |
float (0.0-1.0) | 0.1 |
Maximum acoustic fingerprint dissimilarity; recording match only | |
--keep-in |
directory (repeatable) | Prefer keeping the group member inside this directory | ||
--delete-empty |
flag | false |
Find and delete 0-byte media files | |
-y |
--yes |
flag | false |
Skip the confirmation prompt before deletion |
-q |
--quiet |
flag | false |
Suppress progress bars and scanning messages |
-v |
--verbose |
flag | false |
Print per-file hashes and pairwise distances |
--no-color |
flag | false |
Disable colored terminal output | |
--json |
flag | false |
Output results as JSON instead of a table | |
-h |
--help |
flag | Print help information | |
-V |
--version |
flag | Print version |
Option Details
Section titled “Option Details”--threshold (-t)
Section titled “--threshold (-t)”Controls the maximum hamming distance between two perceptual hashes for image and video files to be considered duplicates. A distance of 0 means the hashes must be identical. The default of 1 tolerates a single bit of difference, which catches files that are visually the same but differ slightly from re-encoding. Audio has its own threshold (--audio-threshold).
Higher values catch more aggressively similar files but increase the risk of false positives.
dublette ~/Photos -t 0 -ndublette ~/Photos -t 3 -n--dry-run (-n)
Section titled “--dry-run (-n)”Shows what would be deleted without actually removing any files. Useful for previewing results before committing. The exit code is 1 when duplicates are found, enabling use in scripts.
dublette ~/Photos -n--only
Section titled “--only”Restrict processing to one media type. Without this flag, images, videos, and audio are all processed in separate passes.
dublette ~/Media --only imagesdublette ~/Media --only videosdublette ~/Media --only audio--audio-match
Section titled “--audio-match”Selects how audio files are compared. Both strategies are explained in Acoustic Fingerprinting.
recording(default): computes an acoustic fingerprint from the decoded audio (up to the first 120 seconds, via ffmpeg) and groups files whose fingerprint dissimilarity is within--audio-threshold. The same recording matches across formats and bitrates – an mp3 rip of a flac is a duplicate.encoding: hashes the encoded audio stream with tag regions excluded. A retagged copy matches; a re-encode never does. Exact, takes no threshold, and needs no ffmpeg.
Recording-match groups keep the highest-fidelity file: lossless (flac, wav, aiff) over lossy, then higher bitrate, then alphabetical. All other groups keep the alphabetically first file.
dublette ~/Music --audio-match encoding -n--audio-threshold
Section titled “--audio-threshold”Maximum normalized dissimilarity (0.0-1.0) between two acoustic fingerprints for the files to be considered the same recording. Applies to recording match only; combining it with --audio-match encoding is an error. The default of 0.1 comfortably matches the same recording across codecs while rejecting different recordings. See Acoustic Fingerprinting for how dissimilarity is computed and a per-value tuning table.
dublette ~/Music --audio-threshold 0.05 -n--keep-in
Section titled “--keep-in”Pins the surviving copy of a duplicate group to a directory. When any member of a group lies under a --keep-in directory, only the members under that directory are candidates to keep; every other member becomes a deletion. Ties among candidates fall back to the normal keep policy (highest fidelity for recording-match audio, alphabetical otherwise). Groups with no member under any --keep-in directory select exactly as without the flag, and grouping itself never changes – the flag affects only which member of a group survives.
The flag is repeatable; when a group has members under several --keep-in directories, the first-listed one wins. Paths are compared after resolving symlinks, so relative paths and trailing slashes work. The directory must exist (exit code 2 otherwise) and does not have to be one of the scanned directories – a subdirectory of a scanned tree is fine.
dublette ~/staging ~/Music --only audio --keep-in ~/Music -yTypical use: deduplicating an inbox of new files against a curated library. Every cross-tree duplicate keeps the library copy, regardless of bitrate or name, so external references to library files (a beets library.db, playlists) stay intact.
--delete-empty
Section titled “--delete-empty”Scans for 0-byte media files and deletes them before deduplication. This is a separate step from duplicate detection. Empty files are identified by extension (same set as normal processing).
dublette ~/Photos --delete-empty -y--yes (-y)
Section titled “--yes (-y)”Skips the interactive confirmation prompt before deleting files. Required for non-interactive environments (scripts, CI).
dublette ~/Photos -y--quiet (-q)
Section titled “--quiet (-q)”Suppresses progress bars and status messages written to stderr. Table output and JSON output are unaffected.
dublette ~/Photos -q -n--verbose (-v)
Section titled “--verbose (-v)”Prints the computed hash for each file and the hamming distance for every pairwise comparison. Output goes to stderr.
dublette ~/Photos -v -n--json
Section titled “--json”Outputs results as a JSON object to stdout instead of a table. Suppresses table output. See JSON Output Format below.
dublette ~/Photos -n --json--no-color
Section titled “--no-color”Disables colored terminal output.
dublette ~/Photos --no-color -nExit Codes
Section titled “Exit Codes”| Code | Meaning |
|---|---|
0 |
Success (no duplicates found, or duplicates deleted successfully) |
1 |
Dry-run found duplicates |
2 |
Invalid argument (missing directories, nonexistent path, not a directory) |
130 |
Interrupted by Ctrl+C |
The exit code 1 in dry-run mode is intentional: it allows scripts to detect whether duplicates exist without deleting them.
JSON Output Format
Section titled “JSON Output Format”When --json is used, stdout contains a single JSON object:
{ "empty_files": ["path/to/empty.jpg"], "groups": [ { "keep": "photos/original.jpg", "duplicates": ["photos/copy.jpg", "photos/another-copy.jpg"] } ], "total_duplicates": 2, "dry_run": false, "skipped": [ { "path": "music/corrupt.mp3", "reason": "decode_failed", "detail": "ffmpeg could not decode audio from music/corrupt.mp3" } ], "total_skipped": 1, "warnings": ["ffmpeg not found on PATH; skipping video pass"]}| Field | Type | Description |
|---|---|---|
empty_files |
array of strings | Relative paths of 0-byte files (only populated with --delete-empty) |
groups |
array of objects | Each group contains a keep path and a duplicates array |
total_duplicates |
integer | Total number of files marked for deletion |
dry_run |
boolean | Whether this was a dry run |
skipped |
array of objects | Files that could not be hashed; each has path, reason, and detail |
total_skipped |
integer | Number of entries in skipped |
warnings |
array of strings | Pass-level warnings, such as an entire media pass being skipped |
All seven fields are always present. skipped and warnings are [] and total_skipped is 0 when a run processes every file.
Skipped files are the reason a result is a floor, not a total: a file that was never fingerprinted is a file whose duplicate cannot have been found. The same entries are also printed to stderr as Warning: skipping <path>: <detail> regardless of --json.
Skip Reasons
Section titled “Skip Reasons”reason is a stable tag safe to branch on. detail is the human-readable message and may change between releases.
| Tag | Meaning |
|---|---|
decode_failed |
The file’s content could not be decoded – corrupt image, ffmpeg decode failure, no usable video frame. Worth investigating |
too_short |
Audio decoded but was too short (under ~3 seconds) to yield an acoustic fingerprint |
unsupported_container |
No tag parser understands the container. Expected for wma under --audio-match encoding |
unreadable |
The file could not be read at all – permissions, I/O error, or a failed tag-region exclusion |
New tags may be added in future releases; a consumer matching on reason should tolerate unknown values.
Supported File Formats
Section titled “Supported File Formats”Images
Section titled “Images”jpg, jpeg, png, bmp, gif, tiff, webp
Videos (requires ffmpeg)
Section titled “Videos (requires ffmpeg)”mp4, mov, avi, mkv, wmv, flv, webm, m4v, 3gp
Audio (recording match requires ffmpeg)
Section titled “Audio (recording match requires ffmpeg)”mp3, flac, ogg, opus, m4a, aac, wav, wma, aiff
Audio tracks embedded in video files are never fingerprinted – a music video and its audio rip are not duplicates of each other. Under encoding match, wma files are reported as skipped (no tag parser support); recording match handles them via ffmpeg.
File extension matching is case-insensitive. A file named PHOTO.JPG is treated the same as photo.jpg.