songleaf

songleaf: dense, readable one-page song sheets from searchable sources.

Find a song, keep it as lyrics plus anchored annotations (sections, chords, provenance, score links), and render it onto one A4 page with the lyrics as large as will fit.

>>> import songleaf
>>> song = songleaf.parse_chords_over_lyrics("C       G\nHello there my friend")
>>> [a.body["symbol"] for a in song.of_kind("chord")]
['C', 'G']

From a query to a PDF, on the Kaggle chords-and-lyrics corpus:

songleaf.sheet("wonderwall oasis")   # -> {'path': '.../sheets/oasis-wonderwall.pdf', ...}

or python -m songleaf sheet "wonderwall oasis".

Functions

chord(at, symbol, *[, timing])

A chord landing on the syllable that starts at offset at.

fit_font_size(song, *[, page_size, margin, ...])

The largest lyric font size at which song fits one page in layout (default: v1's).

get_song(key, *[, sources])

The song named by key ("<source>:<id>"), from the source of that name.

layout_named(spec, *[, style])

The layout of a spec: options of LAYOUT_OPTIONS joined by + ("inline+two-column").

layout_spec(spec)

The one spelling of a layout spec, for names: "Dense" -> "dense".

make_renderer([layout])

A (song, output) -> dict renderer for layout, the shape sheet's renderer= takes.

parse_chords_over_lyrics(raw, *[, meta, ...])

Parse chords-over-lyrics text into a Song.

render_dense_a4(song, output, *[, ...])

Render song in the v1 layout as a PDF to output (a path or a binary file object).

render_sheet(song, output, *[, layout, ...])

Render song in layout (a SheetLayout or a spec) as a PDF to output.

score_link(start, end, *, source, id[, url, ...])

A link from [start, end) to a score snippet held by a source.

search([query, title, artist, lyrics, ...])

Search every source and merge the hits, best first.

section(start, end, label)

A section (verse, chorus, ...) spanning [start, end).

sheet(query, *[, output, pick, refresh, ...])

Make a one-page song sheet (PDF) for the best match of query.

song_store([rootdir])

Songs as JSON files in rootdir (default: the songs data dir).

songs(*[, store])

The keys of the stored songs.

Classes

Annotation(kind, start, end[, body])

One annotation over Song.text[start:end]; a point when start == end.

DenseStyle([lyric_font, chord_font, ...])

Typography of the dense layouts.

Hit(source, id, title[, artist, score, meta])

One search result: a song a source can get().

KaggleChordsSource(*[, loader, min_score])

The Kaggle chords-and-lyrics corpus (~135K songs, chords over lyrics).

SheetLayout([chords, packing, columns, ...])

One way to lay a song out on a page.

Song(text[, annotations, meta, provenance])

Lyrics text plus annotations, metadata and provenance.

class songleaf.Annotation(kind, start, end, body=dict())[source]

Bases: object

One annotation over Song.text[start:end]; a point when start == end.

to_dict()[source]

The JSON-ready form.

Return type:

dict

class songleaf.DenseStyle(lyric_font='DejaVuSans', chord_font='DejaVuSans-Bold', label_font='DejaVuSans-Bold', title_font='DejaVuSans-Bold', lyric_color='#000000', chord_color='#3b73c4', label_color='#8c8c8c', separator_color='#a6a6a6', title_color='#595959', chord_scale=0.62, label_scale=0.5, title_scale=0.6, chord_rise=0.56, chord_halo='', chord_halo_width=0.16, ascent=0.72, descent=0.21, row_gap=0.06, paragraph_gap=0.3, separator=' / ', chord_gap=0.3, bare_chord_gap=0.9, label_gap=0.4, inline_chord_scale=0.7, inline_chord_rise=0.2, inline_chord_pad=0.15, before_marker='‹', column_gap=0.8, column_rule_color='#d9d9d9', chorus_shade='#f3efe4', bridge_shade='#e8eef6', band_pad=0.12, repeat_color='#4d4d4d', tonic_chord_color='#1c4a91', outside_chord_color='#c0602a', staff_space=0.4, snippet_color='#333333')[source]

Bases: object

Typography of the dense layouts. Lengths are fractions of the lyric font size.

class songleaf.Hit(source, id, title, artist='', score=0.0, meta=dict())[source]

Bases: object

One search result: a song a source can get().

property key: str

"<source>:<id>", the song’s name for get_song() and the store.

to_dict()[source]

The JSON-ready form.

Return type:

dict

class songleaf.KaggleChordsSource(*, loader=None, min_score=70)[source]

Bases: object

The Kaggle chords-and-lyrics corpus (~135K songs, chords over lyrics).

Read through sung from a local copy of the zip, with no Kaggle credentials needed when that copy exists. The corpus was scraped from a chords site: personal use only (licence tag gray).

Title and artist are matched fuzzily (typos, punctuation and word order do not matter); lyrics must contain every word of the lyrics query as a whole word. Lyrics matches are not ranked beyond popularity.

Parameters:
  • loader – () -> pandas.DataFrame with columns. Defaults to sung’s loader of the local corpus zip.

  • min_score (float) – Fuzzy-match cutoff, 0-100.

get(song_id)[source]

The song with this id, parsed into a Song.

Return type:

Song

search(query='', *, title='', artist='', lyrics='', limit=10)[source]

Songs matching every given constraint, best first.

Return type:

list[Hit]

search_tiers(query, *, limit=10)[source]

The songs whose title, artist or lyrics match query, by where they match.

{"title": [...], "artist": [...], "lyrics": [...]}, at most limit in each, best first. A song is in the first kind it matches, even when it ranks below the limit there, so a song is never shown as an artist match because more titles matched.

Title and artist match word by word (WordIndex): the query is inside the field ("wonder" finds Wonderwall) or the field is inside the query ("wonderwall oasis" finds the title Wonderwall and the artist Oasis), a word of four letters or more forgiving a typo; or every word of the query is in the title or in the artist ("paper boats invented band"). A song whose title holds a word of the query that its artist does not is a title match ("wonderwall oasis": Wonderwall (ver. 2) by Oasis), otherwise an artist match. Within each kind, the songs whose title and artist together hold the most words of the query come first (so Wonderwall by Oasis leads), then the closest by fuzzy score (of the title, or of title and artist, whichever is closer; of the artist), then the most popular. Lyrics hold every word of the query, rank by popularity, and carry meta["snippet"]: the line that matched, with its spans.

Return type:

dict[str, list[Hit]]

class songleaf.SheetLayout(chords='over', packing='greedy', columns=1, shading=False, diagrams=None, style=DenseStyle(), score=False)[source]

Bases: object

One way to lay a song out on a page.

Parameters:
  • chords (str | ChordPlacement) – Where chords go: "over" (overlapping the lyric row) or "inline" (in the line, before their syllable), or any ChordPlacement.

  • packing (str | Callable) – Which lines share a row: "greedy" or "structured", or any packer with the signature of songleaf.packing.pack_greedy().

  • columns (int) – Columns per page.

  • shading (bool) – Shade sections, repeated lines and chord functions.

  • diagrams (str | None) – "piano" or "guitar" draws the song’s chords as a strip of diagrams at the foot of the first page (songleaf.diagrams); the lyrics shrink to make room. None: no diagrams.

  • score (bool) – Draw the song’s score snippet, if its provider has one, above the line it belongs to (songleaf.snippets); the lyrics shrink to make room.

  • style (DenseStyle) – Typography and colours.

property packer: Callable

The line packer.

property placement: ChordPlacement

The chord placement strategy.

class songleaf.Song(text, annotations=list(), meta=dict(), provenance=dict())[source]

Bases: object

Lyrics text plus annotations, metadata and provenance.

Parameters:
  • text (str) – The lyrics, lines separated by "\n".

  • annotations (list[Annotation]) – Standoff annotations over text (kept sorted by start).

  • meta (dict) – Descriptive fields: title, artist, capo, key, …

  • provenance (dict) – Where the song came from: source, id, url, license.

classmethod from_dict(data)[source]

The inverse of to_dict().

Return type:

Song

lines()[source]

The lines of the text, with their offsets.

Return type:

Iterator[Line]

of_kind(kind)[source]

The annotations of one kind, in text order.

Return type:

list[Annotation]

to_dict()[source]

The JSON-ready form (what the store persists).

Return type:

dict

songleaf.chord(at, symbol, *, timing='at')[source]

A chord landing on the syllable that starts at offset at.

Return type:

Annotation

songleaf.fit_font_size(song, *, page_size='A4', margin=14.0, style=None, layout=None, min_font_size=6.0, max_font_size=72.0, precision=0.05, snippets=None)[source]

The largest lyric font size at which song fits one page in layout (default: v1’s).

style, if given, replaces the layout’s style. Returns min_font_size if even that does not fit (the song then needs more pages). snippets is the score-snippet provider of a score layout (default songleaf.snippets.default_provider()’s).

Return type:

float

songleaf.get_song(key, *, sources=None)[source]

The song named by key ("<source>:<id>"), from the source of that name.

Return type:

Song

songleaf.layout_named(spec, *, style=None)[source]

The layout of a spec: options of LAYOUT_OPTIONS joined by + ("inline+two-column").

Options apply in order, over the v1 layout and style (default DenseStyle).

Return type:

SheetLayout

songleaf.layout_spec(spec)[source]

The one spelling of a layout spec, for names: "Dense" -> "dense".

>>> layout_spec("2col + INLINE"), layout_spec("dense+"), layout_spec("inline+2col+inline")
('two-column+inline', 'dense', 'two-column+inline')
Return type:

str

songleaf.make_renderer(layout='dense', **render_options)[source]

A (song, output) -> dict renderer for layout, the shape sheet’s renderer= takes.

render_options are passed to render_sheet() (page_size, margin, …). A bad spec fails here, not at render time.

>>> make_renderer("packed+shaded").keywords["layout"].packing
'structured'
Return type:

Callable

songleaf.parse_chords_over_lyrics(raw, *, meta=None, provenance=None)[source]

Parse chords-over-lyrics text into a Song.

Parameters:
  • raw (str) – The chord chart, chord lines column-aligned over lyric lines.

  • meta (Mapping | None) – Metadata to start from (title, artist, …); capo, key and tuning found in the text are added to it.

  • provenance (Mapping | None) – Where the text came from, stored as is.

Return type:

Song

songleaf.render_dense_a4(song, output, *, page_size='A4', margin=14.0, style=None, min_font_size=6.0, max_font_size=72.0)[source]

Render song in the v1 layout as a PDF to output (a path or a binary file object).

The same as render_sheet() with layout="dense". Returns {"path", "font_size", "pages", "rows"}; path is None when output is a file object.

Return type:

dict

songleaf.render_sheet(song, output, *, layout='dense', page_size='A4', margin=14.0, min_font_size=6.0, max_font_size=72.0, scale=1.0, snippets=None)[source]

Render song in layout (a SheetLayout or a spec) as a PDF to output.

The lyric font size is the largest that fits the song on one page, times scale: 1 fills the page, 0.8 leaves room, 1.2 is larger type that runs onto a second page. It never goes past max_font_size.

snippets provides a score layout’s snippet: song -> ScoreSnippet | None (default songleaf.snippets.default_provider()’s).

output is a path or a binary file object. Returns {"path", "font_size", "pages", "rows"}; path is None when output is a file object.

Return type:

dict

A link from [start, end) to a score snippet held by a source.

notation holds a short snippet inline (format says in what, such as "abc"); rule names the rule that chose the stretch ("chosen" when picked by hand). Both are left out of the body when empty.

Return type:

Annotation

songleaf.search(query='', *, title='', artist='', lyrics='', limit=10, sources=None)[source]

Search every source and merge the hits, best first.

>>> search("paper boats", sources=[])
[]
Return type:

list[Hit]

songleaf.section(start, end, label)[source]

A section (verse, chorus, …) spanning [start, end).

Return type:

Annotation

songleaf.sheet(query, *, output='', pick=1, refresh=False, layout='dense', transpose=0, scale=1.0, sources=None, store=None, renderer=None)[source]

Make a one-page song sheet (PDF) for the best match of query.

query may also be a song key, as search and songs show them (kaggle_chords:1234): a stored song renders without searching, and the key of a known source is fetched from it directly. pick chooses the n-th best match instead of the best. The song is saved to the store; refresh fetches it from its source again, replacing the stored copy. The PDF goes to output, by default the sheets data directory.

layout is dense (the default), or options joined with +: overlap (chords over the words themselves), inline (chords in the line, before their syllable), packed (rows break where the lyrics do), two-column, shaded (sections, repeated lines and chord functions); for example inline+packed+two-column. A renderer decides the layout itself, so it does not go with layout or scale.

transpose moves every chord by that many semitones (spelled for the new key); the stored song keeps its chords. scale multiplies the font size that fills the page: 0.8 for smaller type, 1.2 for larger type over more pages.

Return type:

dict

songleaf.song_store(rootdir=None)[source]

Songs as JSON files in rootdir (default: the songs data dir).

Return type:

MutableMapping[str, Song]

songleaf.songs(*, store=None)[source]

The keys of the stored songs.

Return type:

list[str]

Modules

chordpro

Parse ChordPro text into a Song.

datasets

Where the datasets data kind lives: local files by default, S3 by configuration.

diagrams

Chord diagrams for the sheet: piano keys or guitar shapes (songleaf#13).

engrave

Score snippets engraved by Verovio: the score extra (pip install songleaf[score]).

export

Export a song as ChordPro or as plain text (chords over lyrics), transposition applied.

harmony

What a chord does in its song's key, so that sheets can shade chords by function.

importers

Turn pasted text into a Song: a registry of format importers.

kaggle_index

A search index of the Kaggle corpus: the same songs and keys, without the corpus in memory.

library

The library's lists: which stored songs are in which lists, shared by everyone using it.

match

Where a query matched: the words it shares with a field, as spans to highlight, and the lyric line.

model

The linked-artifact model: a song is lyrics text plus standoff annotations.

packing

Line packing: which consecutive lines of a paragraph share a row.

parse

Parse chords-over-lyrics text into a Song.

render

Render a song onto one dense page, with the lyrics as large as will fit.

snippets

Score snippets on the sheet (songleaf#12): which stretch of a song, and a small staff for it.

songbook

A songbook: several songs in one PDF, with a table of contents, page numbers and bookmarks.

sources

Where songs are found: sources, and a search that composes them.

store

Where songs persist: a MutableMapping of songs, JSON files by default.

timing

Play-along: a song's media links and line timings (songleaf#30).

tools

The single source of truth for every surface: plain functions, flat arguments, JSON-ready results.

ultimate_guitar

Ultimate Guitar: community chord charts the reader opened themselves (songleaf#28).

viewed

Each viewer's Viewed list: the songs they opened, the most recent first.