Session 3 of 6

Reading the Lyrics

Open a lyrics file, parse each line, and store the timestamped words inside a Track object.

🎯 By the end of this lesson you will be able to…
πŸ” Recall from Lesson 2

In Lesson 2 you pulled information out of a filename using string methods. In this lesson you will do the same thing, but with the contents of a file β€” reading it line by line and extracting two pieces of data from each line.

Part 1 β€” What is in a lyrics file?

Each song on the SD card has a matching lyrics file in /sd/transcripts/. The format looks like this:

0:04    [Intro]
1:19    Well, oh, they might wear classic Reeboks
1:22    Or knackered Converse, or track bottoms stucked in socks
1:26    But I'll be forever grateful for the name I've got

Each line has two parts separated by whitespace (spaces or a tab):

Our goal is to turn each of these lines into a pair of values: the timestamp converted to a number of seconds, and the lyric text. We will store all these pairs in a list.

Part 2 β€” Opening and reading a file

Python's built-in open() function opens a file. Using it inside a with block ensures the file is automatically closed when we are done:

with open("/sd/transcripts/Wonderwall.txt") as f:
    for line in f:
        print(line)   # prints every line in the file
πŸ’‘ Why use with open(...)?

On a microcontroller, leaving files open wastes memory and can cause crashes. The with block guarantees the file is closed even if something goes wrong β€” you never need to call f.close() yourself.

Part 3 β€” Splitting a line into its parts

Each line contains a timestamp and some text. split() without any arguments splits on any whitespace (spaces or tabs). Passing a second argument of 1 tells it to split only at the first whitespace it finds β€” which is exactly what we want:

line = "1:19    Well, oh, they might wear classic Reeboks\n"

parts = line.strip().split(None, 1)
print(parts)
# ['1:19', 'Well, oh, they might wear classic Reeboks']

timestamp = parts[0]  # '1:19'
text      = parts[1]  # 'Well, oh, they might wear classic Reeboks'

Part 4 β€” Converting a timestamp to seconds

The timestamp "1:19" means 1 minute and 19 seconds. We need to convert it to a single number (79 seconds) so we can compare it against how long the song has been playing.

timestamp = "1:19"

minutes, seconds = timestamp.split(":")   # ['1', '19']

total_seconds = int(minutes) * 60 + int(seconds)
print(total_seconds)   # 79
πŸ’‘ Why int()?

split() always gives back strings. The string "1" and the number 1 look the same but behave very differently: "1" + "19" gives "119", not 20. int() converts the string to a proper number.

Part 5 β€” Writing load_lyrics()

Now combine all of the above into a function. It takes a file path, reads every line, and returns a list of (seconds, text) pairs:

def load_lyrics(path):
    """Read a lyrics file and return a list of (seconds, lyric) pairs."""
    entries = []

    with open(path) as f:
        for line in f:
            parts = line.strip().split(None, 1)

            if len(parts) == 2:               # skip blank lines
                minutes, seconds = parts[0].split(":")
                total = int(minutes) * 60 + int(seconds)
                entries.append((total, parts[1]))

    return entries

Part 6 β€” Storing lyrics inside a Track

The Track class already has a lyrics attribute. Update your for loop from Lesson 2 to call load_lyrics() when creating each track:

main.py
from pypod import Track, add_track, list_music_files, music_path, lyrics_path, start


def parse_filename(filename):
    stem     = filename[:-4]
    position = stem.rfind("(")
    title    = stem[:position].strip()
    artist   = stem[position + 1 : -1].strip()
    return title, artist


def load_lyrics(path):
    entries = []
    with open(path) as f:
        for line in f:
            parts = line.strip().split(None, 1)
            if len(parts) == 2:
                minutes, seconds = parts[0].split(":")
                total = int(minutes) * 60 + int(seconds)
                entries.append((total, parts[1]))
    return entries


for filename in list_music_files():
    title, artist = parse_filename(filename)
    track = Track(
        title     = title,
        artist    = artist,
        file_path = music_path(filename),
        lyrics    = load_lyrics(lyrics_path(title)),
    )
    add_track(track)


start()
  1. Add the load_lyrics function to your main.py.
  2. Update the Track() call to include lyrics = load_lyrics(lyrics_path(title)).
  3. Add a print statement inside the loop to show the first lyric entry for each track β€” confirm it is a (seconds, text) pair.
  4. Remove the print once you are happy it works.
πŸ† Challenge