iso-scalpel/README.md

7.1 KiB
Executable File

ISO Scalpel

A disc-image editor for ISO 9660 and UDF, built with PySide6 (Qt 6) and pycdlib. Runs as a graphical application or from the command line.

Author: Jeremy Anderson · info@dcos.net · https://dcos.net License: GPL-2.0


Features

  • ISO 9660 interchange levels 1, 2, 3
  • Rock Ridge 1.09 / 1.12
  • Joliet 1 / 2 / 3
  • UDF 2.50 / 2.60
  • El Torito boot images (BIOS and EFI)
  • Add, extract, rename, delete files and folders
  • Volume metadata editing
  • Filesystem diff between two images
  • Command-line interface
  • Split-navigation GUI with tabs, breadcrumbs, and filtering

Requirements

  • Python 3.10+
  • PySide6 >= 6.6
  • pycdlib >= 1.13

On a minimal Linux install you also need the Qt runtime libraries:

# Debian / Ubuntu
sudo apt install libegl1 libgl1 libglib2.0-0 libfontconfig1 \
                 libdbus-1-3 libxkbcommon0 libxcb-cursor0

Install

tar xzf iso-scalpel-1.1.0.tar.gz
cd iso_scalpel_py
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Running the GUI

python main.py                     # empty window
python main.py my_image.iso        # open an image

The interface is a split-nav file manager: two panes side by side, each with breadcrumb navigation, back/forward/up history, a live filter box, and tabs. Either pane can host the filesystem or the ISO image; press Ctrl+Shift+X to swap them.

Running the CLI

python cli.py --help

Commands:

Command Description
new Create a new ISO image
list List the contents of an image
info Show volume metadata
add Add a file or directory to an image
extract Extract a file or directory
rm Remove an entry
boot Show or configure El Torito boot
diff Compare the filesystems of two images
gui Launch the graphical interface

Examples:

# create a UDF image with a file in it
python cli.py new disc.iso -l MYDISC --joliet 3 --rock-ridge 1.09 --udf 2.60 \
    --add readme.txt

# list contents
python cli.py list disc.iso
python cli.py list disc.iso / --view udf

# show metadata
python cli.py info disc.iso

# add a directory tree
python cli.py add disc.iso ./myfolder /sub

# extract a file
python cli.py extract disc.iso /readme.txt ./out.txt

# remove an entry
python cli.py rm disc.iso /old.txt

# compare two images (filesystem diff)
python cli.py diff old.iso new.iso
python cli.py diff old.iso new.iso --all

The diff command follows the POSIX diff(1) exit-code convention: 0 when the images are identical, 1 when they differ, 2 on error. This makes it scriptable in a shell if / && pipeline.

The diff view

diff compares two images at the filesystem level — which entries were added, removed, or modified — without comparing file contents. The GUI exposes the same comparison under Tools → Compare Images… (Ctrl+D), showing a unified tree with status indicators:

  • + added (only in B)
  • - removed (only in A)
  • M modified (in both, different size or date)
  • = unchanged

Output example:

--- old.iso
+++ new.iso

2 added, 1 removed, 1 modified, 14 unchanged

 + /newfile.txt  (2048 bytes)
 - /oldfile.txt  (1024 bytes)
 M /readme.txt  (12 -> 48 bytes)

(14 entries unchanged)

Keyboard shortcuts

Shortcut Action
Ctrl+N New image
Ctrl+O Open image
Ctrl+S Save
Ctrl+Shift+S Save As…
Ctrl+W Close image / close tab
Ctrl+Q Quit
Alt+← / Alt+→ Back / forward
Alt+↑ Up to parent
Ctrl+L Go to location
Ctrl+Shift+X Swap panes
Ctrl+T New tab
Insert Add to image
Ctrl+E Extract
Ctrl+Shift+N New folder
F2 Rename
Delete Delete
Ctrl+F Filter
Ctrl+A Select all
Alt+Enter Properties
Ctrl+B Boot image
Ctrl+D Compare images
F5 Refresh
Ctrl+, Preferences

Project layout

iso_scalpel_py/
├── main.py                      # GUI entry point
├── cli.py                       # CLI entry point
├── requirements.txt
├── README.md
├── QUICKSTART.md
├── BLOG.md
├── LICENSE
└── iso_scalpel/
    ├── __init__.py              # version metadata
    ├── app.py                   # QApplication + stylesheet
    ├── main_window.py           # split-nav window, menus, toolbar
    ├── iso_handler.py           # pycdlib wrapper (no Qt dependency)
    ├── iso_record.py            # record abstraction
    ├── iso_model.py             # Qt tree model
    ├── diff.py                  # filesystem diff engine (no Qt dependency)
    ├── config.py                # settings persistence
    ├── dialogs/                 # New, Properties, Boot, Extract, Diff, Settings, About
    └── widgets/                 # filesystem + ISO panes

iso_handler.py and diff.py are GUI-agnostic. They never import PySide6, so the entire engine can be driven from a script or test harness with no display attached.

Configuration

Settings are stored as JSON:

  • Linux: ~/.config/iso-scalpel/settings.json
  • macOS: ~/Library/Application Support/iso-scalpel/settings.json
  • Windows: %APPDATA%\iso-scalpel\settings.json

Coding standards

The codebase is held to a fixed set of rules enforced by ruff (configured in pyproject.toml):

  • PEP 8 style and PEP 585 / 604 annotations (list[str], str | None).
  • SEI CERT error handling: no blind except Exception and no silent try/except/pass. pycdlib calls catch a narrow tuple (PyCdlibException, OSError, ValueError, KeyError); top-level CLI and GUI error boundaries opt out per-occurrence with an auditable # noqa: BLE001 -- <reason> comment.
  • MISRA-aligned immutability: no mutable default arguments, no function calls in defaults (parent=QModelIndex() uses a module-level singleton).
  • POSIX discipline: entry-point scripts (main.py, cli.py) carry a shebang on line 1 and the executable bit; cli.py diff exits 0/1/2 per diff(1).
  • Dispatch tables over if/elif chains for status, name-type, and flag parsing (_FLAG_TABLE, _PATH_KWARG, _NAME_TYPE_PROBES, _PKG_MANAGERS, VolumeProperties.extensions).
  • Comprehensions and next() over explicit loops where the body is a pure filter/map (list_dir, available_name_types, from_pycdlib modified-time probe, _human_size).
  • Step-down / guard clauses at choice forks: early return on the unhappy path, main logic at the top indentation level.

Run the checks with:

pip install -e '.[dev]'          # installs pytest + ruff
ruff check .                     # lint (must be clean)
python -m pytest                 # test suite

License

Copyright © 2025 Jeremy Anderson info@dcos.net.

GPL-2.0. See LICENSE for the full text. This program comes with ABSOLUTELY NO WARRANTY.

ISO Scalpel is an independent project. No source code from any other ISO editing tool was used.