The music player "QuodLibet" contains a plugin that synchronizes lyrics to the currently playing song. It achieves this by parsing the data from a .lrc
file. This data however could also be stored in the file metadata, and I want to modify it to account for that.
I did some digging through the pc and I found the .py file containing the plugin. I looked through the code in hopes it would be easy, but I never programmed with python and there's some code that I don't understand. It doesn't look too complicated tho, so I was thinking if you guys could decypher it.
def _build_data(self):
self.text_buffer.set_text("")
if app.player.song is not None:
# check in same location as track
track_name = app.player.song.get("~filename")
new_lrc = os.path.splitext(track_name)[0] + ".lrc"
print_d("Checking for lyrics file %s" % new_lrc)
if self._current_lrc != new_lrc:
self._lines = []
if os.path.exists(new_lrc):
print_d("Found lyrics file: %s" % new_lrc)
self._parse_lrc_file(new_lrc)
self._current_lrc = new_lrc
It seems like it stores the data in new_lrc
, so I tried to change that line in an attempt to have it fetched from the tags:
new_lrc = os.path.splitext(track_name)[0] + ".lrc"
Changed to this:
new_lrc = app.player.song.get("~lyrics")
I verified that ~lyrics
is indeed the correct way to reference the tag since that's the way it's used in other parts of the code.
This worked to some extent. It was an improvement from previous test in the way that it didn't told me there was something undefined, here is what the program tells me when I boot it up:
TypeError: stat: path should be string, bytes, os.PathLike or integer not NoneType
------
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/quodlibet/plugins/events.py", line 141, in __invoke
handler(*args)
File "/usr/lib/python3/dist-packages/quodlibet/ext/events/synchronizedlyrics.py", line 282, in plugin_on_song_started
self._build_data()
File "/usr/lib/python3/dist-packages/quodlibet/ext/events/synchronizedlyrics.py", line 195, in _build_data
if os.path.exists(new_lrc):
File "/usr/lib/python3.6/genericpath.py", line 19, in exists
os.stat(path)
TypeError: stat: path should be string, bytes, os.PathLike or integer, not NoneType
I'll let you decide what you make out of that. Here's another snippet of code that is the other part of the puzzle:
def _parse_lrc_file(self, filename):
with open(filename, 'r', encoding="utf-8") as f:
raw_file = f.read()
raw_file = raw_file.replace("\n", "")
begin = 0
keep_reading = len(raw_file) != 0
tmp_dict = {}
compressed = []
while keep_reading:
next_find = raw_file.find("[", begin + 1)
if next_find == -1:
keep_reading = False
line = raw_file[begin:]
else:
line = raw_file[begin:next_find]
begin = next_find
# parse lyricsLine
if len(line) < 2 or not line[1].isdigit():
continue
close_bracket = line.find("]")
t = datetime.strptime(line[1:close_bracket], '%M:%S.%f')
timestamp = (t.minute * 60000 + t.second * 1000 +
t.microsecond / 1000)
words = line[close_bracket + 1:]
if not words:
compressed.append(timestamp)
else:
tmp_dict[timestamp] = words
for t in compressed:
tmp_dict[t] = words
compressed = []
keys = list(tmp_dict.keys())
keys.sort()
for key in keys:
self._lines.append((key, tmp_dict[key]))
del keys
del tmp_dict
That's the part that made things get difficult, and that's where I'm stuck now. The way I see it, the code is expecting to deal with a file, not a tag, so when it makes it's calls they are not working. Any clues to what I can try?
Doesn't matter guys, I already modified it myself and it works as I want now. Here's a link so you can download it and check it out if you want: GitHub issue