feat(jellyfin): songs and their lyrics, read as subtitles are

A music library's artists and albums open as shows and seasons do, and
can be tied to a studyset. A song plays as sound alone (`/Audio/{id}/
stream`, as stored) with its lyrics as Jellyfin keeps them (`/Audio/{id}/
Lyrics`: an LRC file beside it, or its tags), cut into the dictionary's
words as a video's subtitles are, with no translation. Timed lyrics
follow the song: the line sung large and bright among the others faded,
scrolled to as it comes, a line tapped played from its start; untimed
ones show whole. A song without lyrics shows greyed and doesn't open
(`HasLyrics`), as there is nothing in it to read. Its cover stands where
the picture would, glowing as a video does, and its words are tagged
with the artist, shown, and "artist · title", hidden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-13 19:46:54 +02:00
commit 22f5a4fcd1
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -22,6 +22,9 @@ class FakeJellyfinGateway({
final Map<String, List<JellyfinItem>> tree = const {},
final Map<String, JellyfinPlayback> playbacks = const {},
final Map<(String, int), String> vtt = const {},
/// Each song's lyrics, by its id.
final Map<String, List<({String text, Duration? start})>> lyricsOf = const {},
}) implements JellyfinGateway {
/// When set, every request after signing in fails with it.
JellyfinProblem? fail;
@ -172,6 +175,15 @@ class FakeJellyfinGateway({
return vtt[(playback.itemId, track.index)] ?? 'WEBVTT\n';
}
@override
Future<List<({String text, Duration? start})>> lyrics(
JellyfinAccount account,
String itemId,
) async {
_check(account);
return lyricsOf[itemId] ?? const [];
}
@override
Uri imageOf(JellyfinAccount account, JellyfinItem item, {int height = 360}) =>
fakeJellyfinServer.address.replace(path: '/Items/${item.id}/Images');