Skip to main content

scoreseek

Find a musical score by metadata — across many sources, behind one call.

import scoreseek

hits = scoreseek.search("Cooley's")  # out of the box: The Session (CC-BY-SA)
ref = hits[0]
path = ref.fetch()  # -> a local .abc file
# hand straight to audiate.render(path) to hear it

Every hit is a license-tagged ScoreRef, so public-domain / permissive results stay cleanly separated from copyrighted / gray ones — searches are copyright-safe by default.

Install

pip install scoreseek                # zero required dependencies
pip install "scoreseek[render]"      # + audiate, to render hits to audio

Why

Scores live in many places — public-domain corpora (PDMX, IMSLP, OpenScore), downloadable datasets, chord/lyric collections, and your own folders. scoreseek puts them behind one pluggable-source interface and tags every result with a license, so you can ask for "only what I can legally build on" or opt into copyrighted material explicitly.

Sources

A source answers search(query) -> [ScoreRef] and optionally fetch(ref) -> path. Register any object with that shape.

On by default (zero-config, lightweight, cleanly licensed):

Source What License
thesession thesession.org traditional tunes (ABC) CC-BY-SA 4.0
openscore_lieder ~1,356 OpenScore art songs (compressed MusicXML) CC0

Opt-in (import from scoreseek.sources and register_source(...) — heavier, or license-gated):

Source What License Notes
IMSLPSource IMSLP/Petrucci public-domain classical Public Domain live API; mostly PDF, some MIDI; size-capped fetch (see below)
PDMXSource 250K Public-Domain MusicXML corpus PD / CC0 local corpus — one-time download (see below)
ChordonomiconSource 680K chord progressions CC-BY-NC (gray lane) no title/artist — genre/chord-text search
KaggleChordsSource 135K chords+lyrics songs (via sung) scraped / gray needs Kaggle creds + a ~283 MB download
LocalFolderSource a folder of .mid/MusicXML/ABC/kern files you set it offline
WebMidiSource free-MIDI websites (bitmidi, midis101, midiworld): pop, film, game music scraped / gray HTML scraping, best effort; add sites with MidiSite
LakhMidiSource Lakh MIDI Dataset clean_midi (~17K Artist/Title.mid) scraped / gray local: the 234 MB tarball (searched unextracted) or the folder
from scoreseek import register_source, License
from scoreseek.sources import IMSLPSource, LocalFolderSource

register_source(IMSLPSource())  # opt in to IMSLP
register_source(LocalFolderSource("~/scores", license=License.PUBLIC_DOMAIN))

hits = scoreseek.search("nocturne", composer="Chopin", sources=["imslp"])
hits = scoreseek.search("hey jude", allow_copyrighted=True)  # opt into gray/copyrighted

# film / pop MIDI from the web (gray lane: private study)
from scoreseek.sources import WebMidiSource, LakhMidiSource

register_source(WebMidiSource())
register_source(LakhMidiSource("~/Downloads/clean_midi.tar.gz"))
hits = scoreseek.search("star wars", allow_copyrighted=True)

Choosing an IMSLP file (and not downloading 800 MB)

An IMSLP hit is a whole work page: full scores, parts, arrangements and MIDI renditions, some of them hundreds of MB. Hits carry their formats, ref.files() lists every file with its kind and size, and fetch takes a size cap and a choice strategy:

from scoreseek.picks import smallest

ref = scoreseek.search("Symphony No.5", composer="Beethoven", sources=["imslp"])[0]
ref.formats  # ('pdf', 'zip')
for f in ref.files():  # ScoreFile(filename, kind, size, license, section, ...)
    print(f.kind, f.size, f.section, f.filename)

ref.fetch()  # default: <= 50 MB; a PD/CC0 file first, then MIDI/MusicXML,
# a score over a part, then IMSLP's page order
ref.fetch(fmt="pdf")  # only PDFs (fmt="midi" raises if the page has no MIDI)
ref.fetch(pick=smallest)  # a strategy: (List[ScoreFile]) -> ScoreFile
ref.fetch(pick="PMLP01586-BH1826fsc.pdf")  # a filename from ref.files()
ref.fetch(max_bytes=None)  # no cap

When every candidate is over the cap, fetch raises ScoreTooLargeError naming the smallest ones. If a file IMSLP lists without a size turns out too big while downloading, the next choice is tried. Set source-wide defaults with IMSLPSource(max_bytes=..., pick=...).

max_bytes and pick work on every hit, so one loop serves mixed results:

for ref in scoreseek.search("waltz"):
    path = ref.fetch(max_bytes=5_000_000)  # ScoreTooLargeError if over

Sources that download (IMSLP, OpenScore, The Session, WebMidi, Chordonomicon) default to a 50 MB cap; local corpora (folders, PDMX, Lakh, Kaggle) to none. IMSLP caps the download as it streams; other sources check the fetched file. Hits with several files (IMSLP, PDMX's MusicXML + MIDI) support ref.files() and pick=<filename>; on a one-file source a pick strategy has nothing to choose.

Canonicalize a messy query (MusicBrainz)

normalize=True resolves a fuzzy "title [+ artist]" to a canonical work via MusicBrainz before searching (adds a ~1 s network round-trip), and stamps the resolved MBID onto each hit:

hits = scoreseek.search("moonlight sonata beethovan", normalize=True)

PDMX: a local public-domain corpus

PDMX ships only as bulk tarballs, so PDMXSource searches a local extraction. Download once (index-only = 225 MB; + MusicXML = ~2.1 GB) from Zenodo record 15571083 and point the source at it:

from scoreseek.sources import PDMXSource

register_source(PDMXSource("~/pdmx"))  # dir with PDMX.csv (+ extracted mxl/, mid/)

search() works with just PDMX.csv; fetch() needs the extracted mxl//mid/ trees and otherwise raises with the exact tarball URL to download.

Write your own source

from scoreseek.sources import Source
from scoreseek.base import License


class MySource(Source):
    name = "mine"
    license = License.CC0

    def search(self, query="", *, title="", composer="", limit=10):
        return [self._ref(title="...", id="...", formats=("musicxml",))]

    def fetch(self, ref, *, fmt=None):
        ...
        return "/path/to/score.musicxml"

ScoreRef.fetch enforces max_bytes on the returned file for you (set a class attribute max_bytes for a default cap). Take max_bytes / pick in your own fetch, and implement files(ref), when you can do better: cap a download while it streams, or choose among several files.

License tags

License classifies each hit: PUBLIC_DOMAIN, CC0, PERMISSIVE (CC-BY/-SA, MIT, …), NONCOMMERCIAL (CC-BY-NC), COPYRIGHTED, GRAY (scrapes/unspecified), UNKNOWN.

search(..., allow_copyrighted=False) (the default) is copyright-safe: it returns the commercial-safe lanes (PUBLIC_DOMAIN/CC0/PERMISSIVE) plus UNKNOWN (unclassified provenance, e.g. your own local folders), and drops the restricted lanes — COPYRIGHTED, GRAY, and NONCOMMERCIAL (a known usage restriction). Pass allow_copyrighted=True to include them.

Where it fits

scoreseek is the search stage of a larger pipeline: scoreseek (find a score) → audiate (render to audio) → arioso (AI-enhance).

License

MIT

Metadata

Release files for scoreseek 0.0.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for scoreseek 0.0.7
File Size Uploaded Provenance
scoreseek-0.0.7.tar.gz 41.1 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for scoreseek 0.0.7
File Interpreter ABI Platform
scoreseek-0.0.7-py3-none-any.whl Python 3 none any Details

Total release size: 93.6 kB

Release files / scoreseek-0.0.7.tar.gz

Download URL scoreseek-0.0.7.tar.gz
Size 41.1 kB
Tags Source
SHA-256 checksum
How to use checksums
b0ac4019ee443d37ebdcb554ba55cf61faf7d492c50592d8cb093110fdac34fa
BLAKE2b-256 checksum
How to use checksums
d5a4c16960e8fe8c27b0bde7d6dd53a4fd26c859481dd0a1df9d545fbfae59b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / scoreseek-0.0.7-py3-none-any.whl

Download URL scoreseek-0.0.7-py3-none-any.whl
Size 52.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
efd08d0947797f37154466071e7079f49fa67a1aa0dfbe720aa06d1f5661f532
BLAKE2b-256 checksum
How to use checksums
7556530d9f3afe042b7d2d2c463574d2e0728a30ceee9d4e40882144d7d25bf2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for scoreseek 0.0.7

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page