Multiple artists and/or genres

Indeed there is 10MB limit for cover files.
This is configurable in lms.conf, but I will bump it by default.
For the 2s search, I am able to reproduce on a 1M songs fake DB. Will at least make the request async to not block the UI.

Thanks again for testing! I can add local playlist imports for sure, but lyrics support will come really later, so I guess you can stick with jellyfin for a while :slightly_smiling_face:

There’s no need for that, I was just surprised. 10MB is sensible for keeping loading times reasonable. Of the 72265 Cover.jpgs that I currently have, only 35 are filtered out by that limit and getting those few below 10MB is trivial.

Any time.

Lovely.

The ironic thing is that jellyfin doesn’t support external lyrics yet either. 10.9 will (and the beta of it already does if Tolriq is to be believed) however I run Jellyfin in a freebsd jail and I don’t think I can run the beta there as freebsd ports hardly happen.
If nothing gamebreaking happens I think they aim to release Jellyfin 10.9 stable around may or june (if you want to race them haha). :smiley:

Just thought of this thread as I’m in the process of testing the latest lms version (been a few months since I last did) and saw that the external lyrics functionality was merged into opensubsonic last year.

@itm do you have a rough estimate how long it will take until lms supports external lyrics? I looked at the changelogs and saw no mention of it so far.
I’d also be willing to try and help if that can speed it along. While writing lyrict I spent a fair amount of time dealing with parsing, standardizing etc. external and embedded lyrics.

I also have a minor nag about the lms scanner UI:
grafik
It would be nice if you could add decimal separators here to improve readability.

Hello!
To be honest I have no idea, I don’t even really know the lyrics files: what are the files to parse, what are the formats, how languages are handled, etc. I know about the OS expected responses though.
But yes if you can sum up things for me, I can tell you :slight_smile:

Never ever summon him like that :stuck_out_tongue:

His next post will probably explode my backup sizes :stuck_out_tongue:

For external lyrics it’s usually only about .lrc and .txt files (utf-8) which each share the base name of the song they belong to (which is the only link between them).

song.flac
song.lrc
song.txt

The possible information for .lrc files (which I found hardly any player to fully/correctly support) is described here for .lrc files. It’s not entirely accurate (or adhered to in the wild) concerning the timestamps tho as I found a couple of different formats that different software creates.
For what information the lyrics can generally contain, points 4.9 and 4.10 of the id3v2.3.0 spec are useful.

[mm:ss.xx]lyrics
[m:ss.xx]lyrics
[m:ss.xxx]lyrics
[mm:ss.xxx]lyrics

For each of these there’s also a variant with a space between the timestamp and the lyrics text.

In my script I decided to optionally standardize all these variants before handling them further like this:

# Conform timestamp style to [00:00.000]TEXT
def standardize_timestamps(lyrics):
    match_to_skip = r"^\[(\d{2}:\d{2}\.\d{3})\](?! )(.*)$" # if you want the final version to be [00:00.00] text, change "\d{3})\](?! )" to "\d{2})\] "
    match_to_alter = r"^\[(\d{1,2}:\d{2}\.\d{2,3})\] *(.*)$"
    lines = lyrics.split('\n')
    standardized_lyrics = []

    for line in itertools.islice(lines, 5):
        skip = re.match(match_to_skip, line)
        if skip:
            return lyrics
    for line in lines:
        match = re.match(match_to_alter, line)
        if match:
            time_stamp = match.group(1)
            text = match.group(2)
            time_obj = datetime.strptime(time_stamp, "%M:%S.%f")
            formatted_time = time_obj.strftime("%M:%S.%f")[:-3] # if you want to change the formatting to [00:00.00] text, change "[:-3]" to "[:-4]
            standardized_lyrics.append(f"[{formatted_time}]{text}") # and also change "[{formatted_time}]{text}" to "[{formatted_time}] {text}"
        else:
            standardized_lyrics.append(line)
    return '\n'.join(standardized_lyrics)

I opted for [mm:ss.xxx]lyrics for the final output as it has the highest precision and no pointless space before the lyrics.
However, this only gives you the lines with timestamps and optionally text, not special lines to specify the offset etc…

Since the ID tags are supposed to be in front of the actual lyrics, fetching additional information could be done by matching the first 10 or 20 lines (to prevent parsing countless lines needlessly) like this for the offset for example:

^\[offset: *([+-]\d+)\]$

I do not know how languages are handled in .lrc files, I’ve hardly ever come across .lrc files with any of these tags present. The majority only consists of the timestamps and the text. There’s a serious lack of documentation for external lyrics files.

A matter of preference is if you decide to delete or keep empty lines and lines with only a timestamp for synced lyrics (which give them a verse structure but lead to skipping over lines which some might dislike).
grafik
Personally I prefer them to be displayed with empty lines, which looks like this in the file:

[00:18.047]Your deep lines, your edges, the way you curve
[00:25.727]You pass by crossroads with no return
[00:33.527]And come down in springtime to tell you the truth
[00:41.207]Your branches, your breakthroughs, you draw me into you
[00:48.405]
[00:52.847]Riverine, riverine, changing into something new

In comparison, unsynced lyrics are even less documented and usually only contain the lyrics as plain text with a few tags (I found no syntax for) at the start. There might be a standard but I don’t know it.

Overall, I’d ensure that timestamps, lyrics text and the offset are correctly parsed and wait for people to start complaining about the other tags which will hopefully shine a light on the syntax variations in the wild.

Lyrics can have hours too :slight_smile:

And language are usually with songname.languagecode.lrc

There’s also a few other header values in lyrics files like artists, name, …

Like this?
[hh:mm:ss.xxx]
Never seen one (it’s also not on the wiki page), guess I’ll have to change my regex then.

Is the syntax for that documented somewhere or did you come across it in the wild?

Yes like that, there was an issue here about those used.

And that syntax is well known for subtitles and was adopted for lrc by some.

I did know about the .languagecode.extension naming from subtitles but had not encountered it for .lrc files yet. Are ISO-639-1 or ISO-639-2 codes used? ger/deu vs de is what I mean.

Both and there’s [la:ger] as header too :slight_smile:
And a few other header all in [xxx: yyy] format.

And there’s

[01:54.60]Pa<01:55.32>ro<01:56.15>les

For word positions.

And of course there’s repeating lines :

[01:54][02:36][03:45][04:01] Paroles

Lrc are fun :slight_smile: But I actually support everything :wink:

Thanks for all these valuable information!
Looks like OS just has no support for word times?

No support no, I would say that OS should send the data and it’s up to the client to clean if he does not support them.

@655321 to answer about when it will be implemented, I think it can be done for the next release (likely in a couple of weeks)

Lovely. I’m looking forward to it. Then I can finally ditch jellyfin.
Btw., did you optimize the sync speed since the last time I tested lms? Should have been ~7 months ago. I just did a fresh full sync to my new phone (OnePlus 12R) and it only took 15min to finish the artists/albums/tracks stage for ~906k tracks. Jellyfin takes 50ish min for that and lms used to take around an hour. I’m very impressed!

Hmm yes in v3.52.0, I added a specific hack for Symfonium in the search3 endpoint. The idea was to create a server side context during the scan to make each search3 request time constant.
The bigger the database, the bigger the impact of this optim.
Thanks again for testing!

Will probably tell you when lyrics support is ready. Would you mind beta test it?

That 100% matches my observation. ~7 months ago the lms sync started out very fast (2 refreshes of 1000 entries each per second if I recall it correctly) and got slower and slower as the sync progressed (>4s per refresh near the end of the sync I think). Now it just keeps going at the initial speed.

Sure, I’ll probably have a number of test .lrc files with the more exotic variants that @Tolriq mentioned ready to go by then.

Ironic, while I’ve been testing which player supports what I stumbled upon the fact that the German wiki page for .lrc files is more comprehensive (mentions both the [la:ger] header as well as repeating lines) than the English one.
So far I haven’t found a single player (except Symfonium) that detects this variation of storing the language information:

tracknumber title.extension
tracknumber title.languagecode.lrc

I’ve also tested this.

When creating lyrics in MusicBee, it formats them like this:

[0:25.525]no minutes
[3:55.891]1-digit minutes
[13:13.440]2-digit minutes
[61:42.670]1 hour
[101:39.985]over 100 minutes
[122:30.847]2 hours

while the OpenLyrics Foobar2000 Component writes them like this:

[00:33.56]no minutes
[03:11.98]1-digit minutes
[14:48.45]2-digit minutes
[01:02:13.11]1 hour
[01:41:20.25]over 100 minutes
[02:02:06.95]2 hours

lrcget on the other hand writes them like this:

[00:47.43] no minutes
[03:49.92] 1-digit minutes
[12:22.56] 2-digit minutes
[66:25.60] 1 hour
[107:15.92] over 100 minutes
[141:13.96] 2 hours

While this regex does split all of these into timestamp + lyrics (even tho a timestamp with 3 digit minutes is technically wrong), I’m torn on which of these to consider “best”. Forcing everything into [hh:mm:ss.xxx]lyrics could work but potentially wastes plenty of characters if the song is only 3min long, pointlessly saving hh:m for each line.

\[((?:\d{1,2}:)?\d{1,3}:\d{1,2}\.\d{2,3})\] *(.*)

Good grief I want a standard that people adhere to.