A Bash script that batch-converts disc images to .chd using chdman, then auto-generates .m3u playlists for multi-disc games.
| Input | Systems |
|---|---|
.cue + .bin |
PS1, Saturn, PC Engine CD, and most CD-based consoles |
.gdi + tracks |
Dreamcast |
.iso (≤ 900 MB) |
treated as CD |
.iso (> 900 MB) |
PS2, Xbox, GameCube, PSP (UMD) — treated as DVD |
.toc, .nrg |
CD-based |
chdman (part of MAME tools):
# macOS
brew install rom-tools
# Debian / Ubuntu
sudo apt install mame-tools
# Arch
sudo pacman -S mame-tools
# Fedora
sudo dnf install mame-toolsimage2chd.sh [options] [folder]
folder defaults to the current directory.
| Flag | Default | Description |
|---|---|---|
-m, --mode MODE |
auto |
Force conversion mode: auto, cd, or dvd |
-t, --threshold-mb N |
900 |
ISO size in MB that separates CD from DVD auto-detection |
-b, --backup-dir NAME |
backup |
Name of the subfolder originals are moved into |
-B, --no-backup |
— | Leave originals in place after conversion |
-D, --delete-originals |
— | Delete originals instead of moving them |
-j, --jobs N |
1 |
Convert N games in parallel |
-p, --threads N |
chdman default | chdman internal compression threads (-np) |
-c, --compression CODEC |
zlib |
Compression codec set: zlib (best emulator compatibility), default (chdman's smallest), or any literal codec list (e.g. cdlz,cdzl,cdfl) |
-f, --force |
— | Overwrite existing .chd files |
-n, --dry-run |
— | Show what would happen without touching any files |
-r, --recursive |
— | Walk subfolders to any depth |
--top-only |
— | Only process files in the root folder; ignore all subfolders |
--verify |
— | Run chdman verify on each newly created .chd |
--no-m3u |
— | Skip .m3u playlist generation |
--no-progress |
— | Hide the progress bar (auto-disabled in parallel mode or non-TTY) |
--no-prune |
— | Don't remove empty subfolders left behind after cleanup |
-h, --help |
— | Show help |
Convert everything in the current directory (originals moved to ./backup/):
./image2chd.shConvert a specific folder, keep originals, skip playlists:
./image2chd.sh -B --no-m3u ~/roms/ps1Parallel conversion with 4 jobs, delete originals after success:
./image2chd.sh -j 4 -D ~/roms/ps2Dry run to preview what would be converted:
./image2chd.sh -n ~/roms/saturnForce-overwrite existing .chd files and verify each one:
./image2chd.sh -f --verify ~/roms/dreamcastUse chdman's smaller default codecs instead of zlib (modern emulators only):
./image2chd.sh --compression default ~/roms/ps1The script defaults to zlib compression (-c cdzl for CD images, -c zlib for DVD images) because it's compatible with virtually every emulator that supports CHD — including older or stripped-down forks like AetherSX2 / NetherSX2 on Android, the original PCSX2, and many retro handhelds. The trade-off is roughly 10–20% larger files than chdman's default.
If you only target modern emulators on PC (latest PCSX2, RetroArch, Duckstation, etc.) and want the smallest files, pass --compression default to let chdman pick its own codecs (typically LZMA-based). For full control, pass any literal codec list, e.g. --compression cdlz,cdzl,cdfl.
| Codec preset | Used for | File size | Compatibility |
|---|---|---|---|
zlib (default) |
-c cdzl (CD), -c zlib (DVD) |
Larger | Excellent — works everywhere |
default |
no -c flag |
Smallest | Modern emulators only |
| literal string | passed through verbatim | Varies | You're on your own |
- Discovery — finds all
.cue,.gdi,.iso,.toc, and.nrgfiles in the target folder. Default traversal covers the root folder plus one subfolder level deep. Use-rfor unlimited depth or--top-onlyto restrict to the root only. If a.cueor.gdishares a basename with a.iso, the.isois skipped to avoid duplicate conversions. - Mode detection — for each file, determines whether to call
chdman createcdorchdman createdvd..cue/.gdi/.toc/.nrgalways use CD mode;.isofiles are classified by file size against the threshold. - Conversion — runs
chdmanand writes to a.partialtemp file, renaming it only on success. All.chdfiles are written to the root target folder regardless of where the source was found. Failed conversions leave source files untouched. - Source handling — on success, source files in the root folder are moved to the backup subfolder or deleted immediately. Sources found in subfolders are handled as a unit: the entire subfolder is moved or deleted only after every input inside it has been successfully converted. If any conversion in a subfolder failed, the subfolder is left in place.
- Empty folder pruning — after cleanup, any subfolders left empty (containing only
.DS_Storeat most) are automatically removed. Disable with--no-prune. - Playlist generation — scans the output folder for files matching
Name (Disc N).chdand writes a sortedName.m3ufor any group of two or more discs. Existing.m3ufiles are never overwritten.
Playlists are generated automatically. A game like:
Final Fantasy VII (Disc 1).chd
Final Fantasy VII (Disc 2).chd
Final Fantasy VII (Disc 3).chd
produces Final Fantasy VII.m3u containing the three filenames in disc order. Use --no-m3u to disable this.
- The script is compatible with bash 3.2+ (the version shipped with macOS).
- Progress bar is shown only in single-job mode on an interactive terminal. It auto-disables when running in parallel or when stdout is redirected.
--no-backupand--delete-originalsare mutually exclusive.--recursiveand--top-onlyare mutually exclusive.- Exit code is non-zero if any conversion failed (sequential mode only; parallel mode always exits 0).