This release is a pre-release and may not be stable for production use.
remanence
Read and analyse disk images from Python — floppy and hard disk images from vintage and modern systems alike.
Give it an image file and it will tell you what the file actually is, what disk geometry it records, which drive it came from, and what filesystem is on it — then let you list and read the files inside. It handles raw images, QCOW2, VDI, Heathkit H8D, ZIP and 7z archives, KryoFlux flux captures and P64, and it reads HDOS, CBM DOS and FAT filesystems.
This is the Python interface to remanence-lib, which is written in Rust. The wheel contains everything it needs: no other packages to install, no external tools to call, not even for decompression.
This is an alpha release. The API will change without notice, and installing needs
--pre. The package is currently tested on Windows only — the code for Linux and macOS is there, but it is untested and unsupported for now.
Install
pip install --pre remanence
Needs Python 3.10 or newer. One wheel covers every supported version, and type hints are included, so editors and type checkers work out of the box.
Getting started
You open the file; the library never opens it behind your back. You also tell it what format the file is, and it checks that claim against the file's actual contents — if you say H8D and hand it a ZIP, it says so rather than guessing.
import remanence
print(remanence.formats()) # the formats you can name
session = remanence.Session()
with open("disk.h8d", "rb") as source:
medium = session.load_media(source, "h8d")
# What the file turned out to be, layer by layer, with a confidence
# score and a plain-language reason for each.
for layer in medium.identify().layers:
print(layer.kind, layer.id, layer.confidence)
Because you opened the file, you control whether it can be written to. The library asks your file handle that one question, respects the answer, and takes no lock of its own. Closing your Python file afterwards is safe — the library keeps its own copy of the descriptor.
Reading the disk
Disk geometry comes back with a note of where each number came from, and if two sources disagree, you are told rather than being handed one answer:
geometry = medium.geometry
print(geometry.cylinders, geometry.heads,
geometry.sectors_per_track, geometry.sector_bytes)
for reading in geometry.readings:
print(" ", reading.source, reading.at, reading.detail)
first = medium.read_sector(0, 0, 1) # sectors are numbered from one
Reading the files
Files live behind a partition. An image with no partition table has a single partition covering the whole disk, at index 0. You name the filesystem you expect and the library verifies it:
filesystem = medium.partition(0).filesystem_as("hdos")
for entry in filesystem.entries():
print(entry.name, entry.size_bytes)
# Anything else this filesystem records about the file — an HDOS
# catalog date, its flag letters — in its own terms.
for fact in entry.declared:
print(" ", fact.key, fact.value)
data = filesystem.get_file("HDOS.SYS").bytes()
Drives
You can model the drive a disk belonged in, as specific as the real ones — a Commodore 1541, a Heathkit H-17, a hard disk. Putting a disk in a drive and taking it out again changes nothing about the disk:
device = session.add_device("h17")
print(device.attachment) # heathfloppy0
device.insert(medium.id)
device.eject() # the drive stays, and so does the disk
session.release_media(medium.id) # this is what actually discards it
Identifying a file before you commit to it
discover_media answers "what is this?" on its own, and hands back a
result you can pass straight to a load, so the file is only opened once:
discovery = remanence.discover_media("disk.h8d", writable=False)
print(discovery.article, discovery.accepting_devices, discovery.device_type)
found = session.load_discovery(discovery)
# Or do both at once, when the format says which drive recorded it.
drive = session.add_device_for("disk.h8d", writable=False)
Beyond the basics
There is more than fits here: flux captures decoded down to bit and byte level, KryoFlux capture sets loaded as a group, CBM DOS directories, and creating blank disks from scratch. The full documentation covers all of it with examples.
Links
License
GPL-3.0-only. See LICENSE.
Release files for remanence 0.0.1a6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| remanence-0.0.1a6.tar.gz | 682.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| remanence-0.0.1a6-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
Total release size: 1.9 MB
Release files / remanence-0.0.1a6.tar.gz
| Download URL | remanence-0.0.1a6.tar.gz |
|---|---|
| Size | 682.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
123eac4dd8f20e57a4f9e9706a31017a99ef05b8aeb9a67c761cccbc1ce94176
|
|
BLAKE2b-256 checksum How to use checksums |
d463e395b141204a23a5bf44de529b96701e2290649a2f37c8711c85342a03f1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / remanence-0.0.1a6-cp310-abi3-win_amd64.whl
| Download URL | remanence-0.0.1a6-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
268f9da766aca5e2161c8712042474264071ffbd6b8caf88fa37361ed6707912
|
|
BLAKE2b-256 checksum How to use checksums |
a8d764d5cc8623b8471f927849733da275baf31968588aa26b1694df8a6a5e52
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|