beets-pysacd
A beets plugin that imports SACD ISO images, using pysacd.
beets can't tag an .iso. For each disc image this plugin reads the TOC,
finds the MusicBrainz release and the medium (the SACD stereo or multichannel
layer) that the disc is, converts each selected area to DSF or FLAC in a
staging directory, and imports that through beets' normal importer. Each area
becomes an album of its own, on its own medium of the release.
Install
pip install beets-pysacd
Then enable the plugin, with beets' MusicBrainz plugin, in your beets config:
plugins: musicbrainz sacd
Requirements: Python 3.11 or later, beets 2.12 or later (2.x), pysacd 0.2 or
later (its engine, and libFLAC for format: flac).
The plugin must be importable by the Python that runs beet. With beets from a
Linux distribution's package, that is the system Python, and it may not look
where pip installed the plugin. Fedora's /usr/bin/beet, for example, runs
python3 -sP, which leaves out both ~/.local and /usr/local, so beets
reports that it can't load the sacd plugin. Either install beets and the
plugin into one environment of their own:
pipx install beets
pipx inject beets beets-pysacd
or keep the packaged beets and add pip's directory to its path, for example
PYTHONPATH=/usr/local/lib/python3.14/site-packages:/usr/local/lib64/python3.14/site-packages beet ...
(pysacd's engine is under lib64).
Use
beet sacd-import Album.iso # one image
beet sacd-import ~/rips/ # every .iso under a directory
beet sacd-import -a both -f flac Album.iso
beet sacd-import --release 1d1f0a6e-... Album.iso # use this MusicBrainz release
beet sacd-info Album.iso # print the TOC; changes nothing
beet sacd-info ~/rips/ # ...of every image under a directory
Plain beet import also converts the ISO images it finds (an .iso given on
the command line, or inside a directory being imported) before importing them.
Your other files in the same run are imported exactly as your settings say.
Turn this off with auto: no.
An area that is already in the library is skipped. Imported albums carry two
flexible attributes, sacd_iso_sha (a fingerprint of the disc's TOCs) and
sacd_area (stereo or multichannel), so beet ls sacd_area:multichannel
lists the multichannel albums.
DVD-Audio, DVD-Video and Blu-ray images
Disc-image folders often hold DVD and Blu-ray images next to SACDs. The plugin
identifies them and lists them, but doesn't convert them: beet sacd-info
prints one line for each, and imports log why each was left alone.
== .../Bluray/LIVE_CONCERT.iso
Blu-ray image, volume 'LIVE_CONCERT', 4 playlists: catalogued, not converted
== .../DVDA/SURROUND_MIX.iso
DVD-Audio + DVD-Video image, volume 'SURROUND_MIX', 5 title sets: catalogued, not converted
It reads the image's file system (UDF, including the UDF 2.50 metadata
partition that Blu-ray uses, or ISO 9660) and looks for the standard folders:
AUDIO_TS with AUDIO_TS.IFO for DVD-Audio, VIDEO_TS for DVD-Video and
BDMV for Blu-ray. The count is of DVD title sets or Blu-ray playlists.
This lives in the plugin (beetsplug/_sacd_discimage.py), not in pysacd, and
reads only directory structures, never the audio.
Configuration
sacd:
areas: stereo # stereo | multichannel | both
format: dsf # dsf | flac (24-bit PCM)
pcm_rate: 88200 # flac: 88200 | 176400
pauses: no # include the pauses between tracks (pysacd 0.2.1 or later)
auto: yes # convert ISOs found by plain `beet import`
remove_iso: no # delete an ISO once its import is verified
staging: '' # where to convert; default: a hidden directory in the library directory
musicbrainz: yes # let pysacd find the release (barcode, catalogue number, ISRCs)
contact: '' # contact (e.g. an e-mail address) for pysacd's MusicBrainz User-Agent
threads: 0 # conversion threads; 0 = one per CPU
sacd-import options override areas, format, pcm_rate, pauses and
remove_iso for one run.
Tracks are cut at their timecodes, without the pauses between them, like
pysacd convert. With pauses: yes (or --pauses) each pause goes with the
track before it, exactly as pysacd-extract -b and sacd_extract -b cut them:
use it when your library already holds extracts made that way, so the same
disc gives the same tracks. It needs pysacd 0.2.1 or later.
Keeping or removing ISO images
ISO images are kept by default, and never written to. With remove_iso: yes
(or --remove-iso) an image is deleted only after every selected area has
been imported and verified: an album with that disc's fingerprint and area
exists in the library, with all of the area's tracks, and every file exists.
An import you skip, or one that fails, keeps the image.
Files and staging
Converted files always move from the staging directory into your library,
whatever copy, move or in-place settings you use for other files: the
staging directory is temporary and is deleted afterwards, also after an error.
By default it is created inside the library directory, so the move is a
rename on the same filesystem; set staging to put it elsewhere.
Hybrid SACDs
MusicBrainz lists a hybrid SACD as one release with several media: the CD
layer, the SACD stereo layer and sometimes the SACD multichannel layer, all
with the same recordings. beets puts the tracks of every medium into one
candidate and then matches files to tracks. With the same recordings on every
layer, it maps the SACD files to the CD layer (medium 1), overwrites their
release track IDs, and writes the album-level media as "Media". See the
beets discussion.
This plugin keeps each area on its own medium:
- When pysacd matched the release, it knows the medium: that release's
candidates are cut down to that medium before beets compares them, and the
album's
mediais that medium's format, e.g. "Hybrid SACD (SACD layer, 2 channels)". - Without pysacd's match, the medium is the one SACD-layer medium of the candidate with the area's track count and channel type. If that is ambiguous (a set of two SACDs, say), the candidate is left as beets made it.
- A final check after you choose a match maps the files to the medium if they still point elsewhere, and logs it.
How it works
For each ISO image, and each selected area of it:
- pysacd reads the TOC (read-only) and fingerprints the disc from its TOCs. An area whose fingerprint and area are already in the library is skipped.
- pysacd matches the MusicBrainz release and medium, and converts the area into the staging directory, writing the release's IDs into the files. A match on a medium that isn't an SACD layer (a vinyl, CD or digital edition sharing the catalogue number or barcode) isn't used: the area is converted without IDs and beets matches it the normal way. A match on the CD layer of a hybrid release keeps the release; the area goes to that release's SACD layer with its track count and channels.
- beets imports the staging directory. Because the files agree on the
release ID, beets looks that release up directly. For this one import task
only, the plugin then:
- hides the physical-disc tags from matching. pysacd tags the physical disc ("disc 1 of 1", media "Hybrid SACD"), but MusicBrainz counts each layer as a medium ("disc 2 of 3", "Hybrid SACD (SACD layer, 2 channels)"). beets would count those tags against the right candidate, enough to keep a correct match from being applied automatically. beets writes the medium's own values when it applies the match;
- cuts the release's candidates down to the area's medium (see Hybrid SACDs);
- counts only an album on the same medium as a duplicate. beets' default duplicate keys are album artist and album, which both areas of a disc (and a CD rip of the same release) share: without this, importing the multichannel area after the stereo one would be skipped as a duplicate. Another copy of the same layer is still a duplicate;
- moves the files into the library (see Files and staging).
- The album gets
sacd_iso_shaandsacd_area; withremove_iso, the ISO is deleted once every area is verified. The staging directory is removed.
Nothing here changes how beets treats your other files in the same import.
Development
pip install -e '.[dev]'
ruff check . && mypy beetsplug
PYSACD_TESTS=/path/to/pysacd/tests pytest -q
The tests build synthetic SACD images with pysacd's test generator
(tests/sacdgen.py in the pysacd source tree, found through PYSACD_TESTS,
default ../pysacd/tests) and import them into a throwaway beets library.
MusicBrainz is mocked with a hybrid release whose media share recordings, so
the tests need no network. DVD and Blu-ray images for the cataloguing tests
come from tests/discgen.py, which writes minimal UDF and ISO 9660 file
systems.
Licence
GPL-2.0-or-later (see LICENSE). The plugin imports pysacd, which is GPL-2.0-or-later.
Metadata
Release files for beets-pysacd 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| beets_pysacd-0.1.1.tar.gz | 30.3 kB | With provenance | Details for beets_pysacd-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| beets_pysacd-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for beets_pysacd-0.1.1-py3-none-any.whl |
Total release size: 54.7 kB
Release files / beets_pysacd-0.1.1.tar.gz
| Download URL | beets_pysacd-0.1.1.tar.gz |
|---|---|
| Size | 30.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e8f80d7dc84de42ecf9ca57d58fa5e9631bc99cda4349f3d10a720a0dfcd851a
|
|
BLAKE2b-256 checksum How to use checksums |
2f6d696f2f2d14ee8063426e264f434cc2923fce0f4bc2b595d7951a9176e613
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logRelease files / beets_pysacd-0.1.1-py3-none-any.whl
| Download URL | beets_pysacd-0.1.1-py3-none-any.whl |
|---|---|
| Size | 24.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dc1e749e7b5d879302437d732d8a7a12dd16e27f73a3a48b72b0fc4216480dfc
|
|
BLAKE2b-256 checksum How to use checksums |
557cf774fe5e8a0580433cdc0e3f49e214b03f1aeaa8e794de31963047ab6ed4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logSecurity for beets-pysacd 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for beets_pysacd-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.