5.9 KiB
Executable File
ISO Scalpel 1.1.0
I just released ISO Scalpel 1.1.0 — a disc-image editor I wrote in Python. It edits ISO 9660 and UDF images through a graphical interface or a command-line tool. No web browser, no Electron, no runtime dependencies beyond Python and Qt.
Why I Built It
I wanted a graphical ISO editor that supported UDF. The existing GUI editors are stuck in the mid-2000s — GTK+ 2 interfaces, no UDF, no EFI boot, and codebases that haven't seen a commit in years. The command-line tools (xorriso, genisoimage) work, but nobody can remember the flags.
Architecture
ISO Scalpel is split into two layers. iso_handler.py is the engine — it wraps pycdlib and never imports PySide6. Every operation (create, open, save, add, remove, rename, extract, boot, metadata) goes through this one module. diff.py is the same: a filesystem diff engine with no Qt dependency. The GUI layer (main_window.py, the panes, the dialogs) is a thin shell that calls into the handler and redraws.
This matters because the build environment I develop in has no display server. I can run the full test suite — every feature, including UDF and boot — without a GUI. The same engine powers the CLI.
The GUI uses a split-navigation layout: two panes, each with breadcrumb navigation, back/forward/up history, a live filter box, and tabs. Either pane can host the filesystem or the ISO image, and they can be swapped with one keystroke. I did not copy the layout of any existing ISO editor.
What's New in 1.1.0
Two features: a command-line interface and a filesystem diff mode.
The CLI (cli.py) exposes every operation the GUI does. new, list, info, add, extract, rm, boot, and diff subcommands, each with options for the naming convention and extensions. You can build an image, add files to it, configure boot, and save it from a single command. The CLI is the same engine as the GUI — no duplicated logic.
The diff mode compares two ISO images at the filesystem level. It walks both directory trees, matches entries by path, and reports which were added, removed, modified, or unchanged. It does not compare file contents — only names, sizes, and dates. The output looks like a unified diff:
--- 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)
In the GUI, the same comparison is available under Tools → Compare Images (Ctrl+D). It opens a dialog where you pick two images and get a color-coded tree: green for added, red for removed, amber for modified, grey for unchanged.
The Engine
pycdlib treats ISO 9660, Rock Ridge, Joliet, and UDF as four parallel directory trees. When you add a file, it gets written into all four — with the same data, but four directory entries pointing at it. The handler keeps an in-memory tree that tracks each directory's path in every convention, so a single add_file call resolves the path across all four and hands them to pycdlib together.
ISO 9660 level 1 names are mangled to upper-case 8.3 with a ;1 version suffix. My Vacation Photos.jpeg becomes MY_VACAT.JPE;1. The handler checks for collisions and appends a numeric suffix (MY_VACAT_2.JPE;1) until it finds a free slot. The original name is preserved in the Rock Ridge, Joliet, and UDF trees.
El Torito boot is supported for both BIOS (platform ID 0) and EFI (platform ID 0xEF), with the boot-info-table patch that ISOLINUX images expect. The boot catalog is generated for every enabled naming convention.
Build It
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
python main.py # GUI
python cli.py --help # CLI
Config lives at ~/.config/iso-scalpel/settings.json and is created on first run.
Quality Assurance Pass
The 1.1.0 release went through a senior-team QA pass — a QA analyst, a Linux engineer, an architect, an admin, and a DevOps project manager each signed off on production readiness. The pass applied PEP 8, POSIX, SEI CERT, and MISRA-aligned discipline uniformly:
- Dispatch tables over if/elif chains. Flag parsing, name-type resolution, distro package-manager selection, and diff-status formatting are all flat lookup tables now, so adding a switch or a distro is a one-line append.
- Comprehensions and
next()over explicit loops in the pure filter/map spots (list_dir,available_name_types, the modified-time probe infrom_pycdlib). - Narrow exception handling. Every
except Exceptionwas either narrowed to a specific tuple (PyCdlibException,OSError,ValueError,KeyError) or marked as a deliberate top-level error boundary with an auditable# noqa: BLE001 -- <reason>comment. This surfaced two latent bugs the blind excepts had been hiding:_read_boot_infoprobed a non-existent pycdlib attribute, and_image_sizetreatedlogical_block_sizeas a property when it is a method. Both are fixed. - Step-down / guard clauses at every choice fork, with the unhappy path returning early so the main logic sits at the top indentation level.
- Locked-in standards. A
[tool.ruff]block inpyproject.tomlselects the E/W/F/I/B/C4/SIM/UP/S/BLE/RUF/EXE rule families and per-file ignores for the legitimate exceptions (tests useassert; entry points insert onsys.pathbefore imports).ruff check .is part of the definition of done.
What's Next
The roadmap for 1.2 is straightforward: add file-content diffing as an option (byte-for-byte and SHA-256), add a verify-image command that checks the filesystem against the recorded metadata, and improve the CLI output formatting. I'm also considering an iso-scalpel console-script entry point so pip install gives you a command on your PATH.
The repository is at git.dcos.net. It's GPL-2.0, and contributions are welcome.