Skip to main content

undisorder

Media organizer for photos, videos, and audio. Deduplicates via SHA256, sorts by EXIF date and embedded audio tags, identifies unknown audio via AcoustID/MusicBrainz, and imports into a clean directory structure.

Motivation

Years of digital life leave behind a mess: phone backups scattered across drives, camera dumps in nested DCIM folders, music libraries with broken tags, duplicates everywhere. Existing tools either focus on one media type, require a GUI, or want to manage your collection permanently.

undisorder is a one-shot import tool. Point it at a source, let it deduplicate, sort, and copy. After import, the tool is done -- your files live in a plain directory structure that works with any file manager, photo organizer, or music player. No database lock-in, no daemon, no ongoing dependency.

Disclaimer

This tool copies and moves files. Use --dry-run to verify before executing. The dupes --delete command permanently deletes files. There is no undo.

The --identify option overwrites embedded audio tags in target files with data from MusicBrainz. Original source files are not modified when using --move (copy-then-delete).

AcoustID and MusicBrainz lookups depend on external services. Results may be incomplete or incorrect.

No warranty. See LICENSE for details. If you encounter bugs or unexpected behavior, please report them at https://github.com/rnixx/undisorder/issues.

Features

  • Deduplication -- two-phase detection (file size grouping + SHA256 hashing) finds byte-identical duplicates across directories. Import skips files already in the target via a central hash index.
  • Photo/video organization -- extracts EXIF dates, creates YYYY/YYYY-MM_Topic/ directory structures using meaningful source folder names. Supports JPEG, PNG, HEIC, RAW (CR2, CR3, NEF, ARW, DNG, ...), MP4, MOV, MKV, and more.
  • Audio organization -- reads embedded tags (ID3, Vorbis, MP4 atoms) via mutagen, organizes into Artist/Album/NN_Title.ext.
  • Audio identification -- AcoustID fingerprinting + MusicBrainz lookup overwrites missing/inaccurate tags and writes improved metadata into target files.
  • SQLite hash index -- tracks original_hash and current_hash per file. Dedup works even after external metadata edits. Incremental rebuild via hashdb command.
  • Exclude patterns -- glob-based file and directory filtering (case-insensitive).
  • Interactive selection -- review and accept/skip source directories before import.
  • Batch processing -- processes one source directory at a time (deepest first), 100 files per batch (photos/videos), 10 per batch (audio). One failed batch does not stop the rest.
  • Dry-run mode -- preview all actions without executing.
  • Copy or move -- default is copy. With --move, files are copied first, then source is deleted (safe for --identify workflows).

Quickstart

Prerequisites

Python 3.11+, exiftool, chromaprint:

# Ubuntu/Debian
sudo apt install libimage-exiftool-perl libchromaprint-dev

# Arch
sudo pacman -S perl-image-exiftool chromaprint

# macOS
brew install exiftool chromaprint

Install

pip install undisorder

Install from source

git clone https://github.com/rnixx/undisorder.git
cd undisorder
make install
source venv/bin/activate

Typical workflow

# 1. Remove duplicates in source (keeps oldest by mtime)
undisorder dupes /mnt/backup --delete

# 2. Preview import
undisorder import /mnt/backup --select --dry-run

# 3. Import for real
undisorder import /mnt/backup \
    --select \
    --identify \
    --exclude '*.wav' --exclude-dir 'DAW*'

Commands

undisorder dupes <source> [--delete]

Find byte-identical duplicates in <source>. Groups files by size, then hashes same-size files with SHA256. With --delete, keeps the oldest copy (by mtime) and deletes the rest.

Does not detect acoustic duplicates (same song, different encoding).

undisorder import <source> [OPTIONS]

Import photos, videos, and audio from <source> into organized target directories.

undisorder hashdb <target>

Incremental rebuild of the hash index for <target>. Updates hashes for known files, adds new files, removes records for deleted files. Run this after editing metadata on imported files.

undisorder --configure

Interactive configuration wizard. Creates or updates ~/.config/undisorder/config.toml.

Options

Global

Option Description
--verbose, -v Debug output
--quiet, -q Suppress informational output, warnings/errors
--configure Interactive configuration setup

import

Option Default Description
--dry-run off Preview changes without executing
--move off Move files instead of copying
--images-target PATH ~/Bilder/Fotos Target directory for photos
--video-target PATH ~/Videos Target directory for videos
--audio-target PATH ~/Musik Target directory for audio
--identify / --no-identify off AcoustID + MusicBrainz identification
--acoustid-key KEY -- AcoustID API key (overrides env/cfg)
--exclude PATTERN -- Exclude files by glob (repeatable)
--exclude-dir PATTERN -- Exclude dirs by glob (repeatable)
--select off Interactive directory selection

dupes

Option Description
--delete Delete newer duplicates, keep oldest by mtime

AcoustID API key

Resolved in order:

  1. --acoustid-key CLI flag
  2. ACOUSTID_API_KEY environment variable
  3. acoustid_key in config.toml

Get a free key at https://acoustid.org/new-application.

Configuration

~/.config/undisorder/config.toml (override with $UNDISORDER_CONFIG_DIR or $XDG_CONFIG_HOME):

images_target = "~/Bilder/Fotos"
video_target = "~/Videos"
audio_target = "~/Musik"
identify = true
acoustid_key = "your-api-key"
exclude = ["*.wav", "*.aiff"]
exclude_dir = ["DAW*"]

CLI flags override config values. List fields (exclude, exclude_dir) are merged from CLI and config.

Directory structures

Photos and videos

~/Bilder/Fotos/
├── 2023/
│   ├── 2023-08_Urlaub-Kroatien/
│   └── 2023-12/
└── 2024/
    └── 2024-07_Geburtstag/

Date from EXIF (DateTimeOriginal, CreateDate, QuickTime, XMP), fallback to file mtime. Directory name from source folder if meaningful, otherwise plain YYYY-MM. Generic names (DCIM, Camera, downloads, ...) and camera folder patterns (100APPLE, 101_PANA, ...) are ignored.

Audio

~/Musik/
├── The Beatles/
│   └── Abbey Road/
│       ├── 01_Come Together.mp3
│       └── 02_Something.mp3
└── Unknown Artist/
    └── Unknown Album/
        └── untitled_track.mp3

Path: {target}/{Artist}/{Album}/{NN_Title}{ext}. Falls back to Unknown Artist / Unknown Album for missing tags.

Development

Setup

git clone https://github.com/rnixx/undisorder.git
cd undisorder
make install

QA

make test        # Run tests
make check       # Linting (ruff + isort)
make typecheck   # Type checking (mypy)
make format      # Auto-fix lint issues

CI runs all checks via GitHub Actions on push and pull request (Python 3.11, 3.12, 3.13).

Manual testing

The testing/ directory contains scripts for end-to-end testing with real media files.

Additional system dependencies

The test setup script (testing/setup.sh) generates synthetic media files and requires these tools in addition to the runtime prerequisites:

  • ffmpeg -- with lavfi input support, libx264 video codec, and aac audio codec (standard in most distribution packages)
  • exiftool -- already required at runtime, also used to stamp EXIF dates on generated test images
# Ubuntu/Debian
sudo apt install ffmpeg libimage-exiftool-perl

# Arch
sudo pacman -S ffmpeg perl-image-exiftool

# macOS
brew install ffmpeg exiftool

Running

testing/setup.sh     # Create test data in /tmp/undisorder-test/
testing/run.sh       # Run all manual tests
testing/verify.sh    # Verify results

For AcoustID identification tests, set ACOUSTID_API_KEY before running.

Author

Robert Niederreiter rnix@squarewave.at

License

GPL-2.0-only

Release files for undisorder 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for undisorder 1.0.0
File Size Uploaded
undisorder-1.0.0.tar.gz 34.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for undisorder 1.0.0
File Interpreter ABI Platform
undisorder-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 69.1 kB

Release files / undisorder-1.0.0.tar.gz

Download URL undisorder-1.0.0.tar.gz
Size 34.2 kB
Tags Source
SHA-256 checksum
How to use checksums
279b6d39fdbd8804b4700dfbd12b0e0fa0e8dd1fb3bb7deeaf3e5f49f8b5e74b
BLAKE2b-256 checksum
How to use checksums
ba56e1206f760ee8d446a0bcf801fdfad5d0ccfe968a2594af7d8fb7037c1583
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release files / undisorder-1.0.0-py3-none-any.whl

Download URL undisorder-1.0.0-py3-none-any.whl
Size 35.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3928640a32eeda0f3d147b524ec2404ba5ccd1749d9496f5e062907ad940436f
BLAKE2b-256 checksum
How to use checksums
8d568ba19698a066ca72b04bd1583650e56a46ec89dd712ec4f68646621f1826
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page