songleaf.timing¶
Play-along: a song’s media links and line timings (songleaf#30).
Media (
with_media()): the song’s recording on YouTube or Spotify, as ids inmeta["media"]({"youtube": "dQw4w9WgXcQ"}), checked against each service’s id format so the page can embed a player without trusting free text.Timings (
with_lrc()): an.lrcfile (or LRCLIB’s synced lyrics) gives a time per lyric line;align_lrc()matches its lines to the song’s lines by word overlap (token F1, in order, skipping what does not match), and each matched line gets a"time"annotation: a point at the line’s start,body={"ms": 61230, "source": "lrc"}. **Only the times are kept, never the LRC’s text**, so a song’s lyrics stay the ones it was imported with.
The page then follows the recording: the line whose time has come scrolls into view.
>>> from songleaf.model import Song
>>> song = Song("Paper boats on a stream\ncarry the names of a dream")
>>> lrc = "[ar:Someone]\n[00:12.50]Paper boats on the stream\n[00:17.00]Carry the names of my dream"
>>> timed = with_lrc(song, lrc)
>>> [(a.start, a.body["ms"]) for a in timed.of_kind("time")]
[(0, 12500), (24, 17000)]
Module Attributes
Where each service's id sits in a link to it (a watch, share, embed or track URL). |
|
how |
Functions
|
|
|
LRCLIB's synced lyrics (LRC text) for a song, or None. |
|
The service id in text: an id, or a link to it (watch, share, embed or track URL). |
|
|
|
Word-overlap F1 of two lines (case and punctuation ignored). |
|
|
|
|
- songleaf.timing.GetJson¶
how
fetch_lrclib()reaches the network (inject it).- Type:
(url, params) -> JSON
- songleaf.timing.MEDIA_LINKS: dict[str, Pattern] = {'spotify': re.compile('(?:/track/|spotify:track:)([A-Za-z0-9]{22})(?![A-Za-z0-9])'), 'youtube': re.compile('(?:youtu\\.be/|[?&]v=|/embed/|/shorts/|/live/)([A-Za-z0-9_-]{11})(?![A-Za-z0-9_-])')}¶
Where each service’s id sits in a link to it (a watch, share, embed or track URL).
- songleaf.timing.align_lrc(song, lrc, *, min_f1=0.5, lookahead=4)[source]¶
(line start offset, ms)pairs: LRC lines matched in order to the song’s lines.Each LRC line is matched to the best of the next
lookaheadunmatched song lines with lyrics, if its token F1 is at leastmin_f1; an LRC line that matches none is skipped (an ad lib, a line the song does not have).
- songleaf.timing.fetch_lrclib(title, artist, *, duration_s=None, get_json=None)[source]¶
LRCLIB’s synced lyrics (LRC text) for a song, or None. Use it with
with_lrc(), which keeps the times only.
- songleaf.timing.media_id(service, text)[source]¶
The service id in text: an id, or a link to it (watch, share, embed or track URL).
>>> media_id("youtube", "https://youtu.be/dQw4w9WgXcQ?t=3") 'dQw4w9WgXcQ' >>> media_id("spotify", "https://open.spotify.com/track/4uLU6hMCjMI75M1A2tKUQC?si=x") '4uLU6hMCjMI75M1A2tKUQC'
- Return type:
- songleaf.timing.parse_lrc(text)[source]¶
(milliseconds, line text)per timed line of an LRC, sorted by time.A line with several time tags (a repeated chorus) appears once per tag; an
[offset:+/-ms]tag shifts every time (positive: earlier, as the format says); metadata tags ([ar:...]) and untimed lines are skipped.
- songleaf.timing.token_f1(a, b)[source]¶
Word-overlap F1 of two lines (case and punctuation ignored).
>>> token_f1("Carry the names", "carry the NAMES of my dream") 0.6667
- Return type: