From 814161d78db0dc07c16fa6e80f6ff1710866e31b Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Thu, 28 Dec 2023 01:20:29 +0000 Subject: [PATCH] Add OS Lyrics extension (#2656) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * draft commit * time to fight pipeline * round 2 changes * remove unnecessary line * fight taglib. again * make taglib work again??? * add id3 tags * taglib 1.12 vs 1.13 * use int instead for windows * store as json now * add migration, more tests * support repeated line, multiline * fix ms and support .m, .mm, .mmm * address some concerns, make cpp a bit safer * separate responses from model * remove [:] * Add trace log * Try to unblock pipeline * Fix merge errors * Fix SIGSEGV error (proper handling of empty frames) * Add fallback artist/title to structured lyrics * Rename conflicting named vars * Fix tests * Do we still need ffmpeg in the pipeline? * Revert "Do we still need ffmpeg in the pipeline?" Yes we do. This reverts commit 87df7f6df79bccee83f48c4b7a8118a7636a5e66. * Does this passes now, with a newer ffmpeg version? * Revert "Does this passes now, with a newer ffmpeg version?" No, it does not :( This reverts commit 372eb4b0ae05d9ffe98078e9bc4e56a9b2921f32. * My OCD made me do it :P --------- Co-authored-by: Deluan Quintão --- .github/workflows/pipeline.yml | 18 +- core/ffmpeg/ffmpeg.go | 6 + .../20231209211223_alter_lyric_column.go | 82 +++++++ model/lyrics.go | 201 ++++++++++++++++++ model/lyrics_test.go | 104 +++++++++ model/mediafile.go | 12 +- scanner/mapping.go | 2 +- scanner/metadata/ffmpeg/ffmpeg_test.go | 31 +++ scanner/metadata/metadata.go | 43 +++- scanner/metadata/metadata_test.go | 133 +++++++++++- scanner/metadata/taglib/taglib_test.go | 64 ++++-- scanner/metadata/taglib/taglib_wrapper.cpp | 85 +++++++- scanner/metadata/taglib/taglib_wrapper.go | 33 +++ scanner/metadata/taglib/taglib_wrapper.h | 2 + server/subsonic/api.go | 1 + server/subsonic/helpers.go | 42 ++++ server/subsonic/media_retrieval.go | 52 +++-- server/subsonic/media_retrieval_test.go | 177 +++++++++++++-- server/subsonic/opensubsonic.go | 1 + ...es LyricsList with data should match .JSON | 43 ++++ ...ses LyricsList with data should match .XML | 20 ++ ...LyricsList without data should match .JSON | 8 + ... LyricsList without data should match .XML | 3 + server/subsonic/responses/responses.go | 21 ++ server/subsonic/responses/responses_test.go | 65 ++++++ .../01 Invisible (RED) Edit Version.m4a | Bin 18051 -> 18051 bytes .../invalid-files/test-invalid-frame.mp3 | Bin 0 -> 121385 bytes tests/fixtures/test.aiff | Bin 89458 -> 89458 bytes tests/fixtures/test.flac | Bin 21225 -> 21225 bytes tests/fixtures/test.m4a | Bin 18051 -> 18051 bytes tests/fixtures/test.mp3 | Bin 51876 -> 51876 bytes tests/fixtures/test.ogg | Bin 6333 -> 5534 bytes tests/fixtures/test.wav | Bin 89590 -> 89590 bytes tests/fixtures/test.wma | Bin 21431 -> 21581 bytes tests/fixtures/test.wv | Bin 22949 -> 23008 bytes tests/mock_ffmpeg.go | 4 + ui/src/reducers/playerReducer.js | 33 ++- 37 files changed, 1215 insertions(+), 71 deletions(-) create mode 100644 db/migration/20231209211223_alter_lyric_column.go create mode 100644 model/lyrics.go create mode 100644 model/lyrics_test.go create mode 100644 server/subsonic/responses/.snapshots/Responses LyricsList with data should match .JSON create mode 100644 server/subsonic/responses/.snapshots/Responses LyricsList with data should match .XML create mode 100644 server/subsonic/responses/.snapshots/Responses LyricsList without data should match .JSON create mode 100644 server/subsonic/responses/.snapshots/Responses LyricsList without data should match .XML create mode 100644 tests/fixtures/invalid-files/test-invalid-frame.mp3 diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index a8c69daf..11ab19b6 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -1,4 +1,4 @@ -name: 'Pipeline: Test, Lint, Build' +name: "Pipeline: Test, Lint, Build" on: push: branches: @@ -13,6 +13,9 @@ jobs: name: Lint Go code runs-on: ubuntu-latest steps: + - name: Update ubuntu repo + run: sudo apt-get update + - name: Install taglib run: sudo apt-get install libtag1-dev @@ -48,10 +51,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go_version: [1.21.x,1.20.x] + go_version: [1.21.x, 1.20.x] steps: + - name: Update ubuntu repo + run: sudo apt-get update + - name: Install taglib - run: sudo apt-get install libtag1-dev + run: sudo apt-get install libtag1-dev ffmpeg - name: Check out code into the Go module directory uses: actions/checkout@v3 @@ -75,14 +81,14 @@ jobs: name: Build JS bundle runs-on: ubuntu-latest env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 18 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' + cache: "npm" + cache-dependency-path: "**/package-lock.json" - name: npm install dependencies run: | diff --git a/core/ffmpeg/ffmpeg.go b/core/ffmpeg/ffmpeg.go index f81435e0..ea86978b 100644 --- a/core/ffmpeg/ffmpeg.go +++ b/core/ffmpeg/ffmpeg.go @@ -22,6 +22,7 @@ type FFmpeg interface { ConvertToFLAC(ctx context.Context, path string) (io.ReadCloser, error) Probe(ctx context.Context, files []string) (string, error) CmdPath() (string, error) + IsAvailable() bool } func New() FFmpeg { @@ -78,6 +79,11 @@ func (e *ffmpeg) CmdPath() (string, error) { return ffmpegCmd() } +func (e *ffmpeg) IsAvailable() bool { + _, err := ffmpegCmd() + return err == nil +} + func (e *ffmpeg) start(ctx context.Context, args []string) (io.ReadCloser, error) { log.Trace(ctx, "Executing ffmpeg command", "cmd", args) j := &ffCmd{args: args} diff --git a/db/migration/20231209211223_alter_lyric_column.go b/db/migration/20231209211223_alter_lyric_column.go new file mode 100644 index 00000000..db1a1613 --- /dev/null +++ b/db/migration/20231209211223_alter_lyric_column.go @@ -0,0 +1,82 @@ +package migrations + +import ( + "context" + "database/sql" + "encoding/json" + + "github.com/navidrome/navidrome/model" + "github.com/pressly/goose/v3" +) + +func init() { + goose.AddMigrationContext(upAlterLyricColumn, downAlterLyricColumn) +} + +func upAlterLyricColumn(ctx context.Context, tx *sql.Tx) error { + _, err := tx.ExecContext(ctx, `alter table media_file rename COLUMN lyrics TO lyrics_old`) + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, `alter table media_file add lyrics JSONB default '[]';`) + if err != nil { + return err + } + + stmt, err := tx.Prepare(`update media_file SET lyrics = ? where id = ?`) + if err != nil { + return err + } + + rows, err := tx.Query(`select id, lyrics_old FROM media_file WHERE lyrics_old <> '';`) + if err != nil { + return err + } + + var id string + var lyrics sql.NullString + for rows.Next() { + err = rows.Scan(&id, &lyrics) + if err != nil { + return err + } + + if !lyrics.Valid { + continue + } + + lyrics, err := model.ToLyrics("xxx", lyrics.String) + if err != nil { + return err + } + + text, err := json.Marshal(model.LyricList{*lyrics}) + if err != nil { + return err + } + + _, err = stmt.Exec(string(text), id) + if err != nil { + return err + } + } + + err = rows.Err() + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, `ALTER TABLE media_file DROP COLUMN lyrics_old;`) + if err != nil { + return err + } + + notice(tx, "A full rescan will be performed to pick up additional lyrics (existing lyrics have been preserved)") + return forceFullRescan(tx) +} + +func downAlterLyricColumn(ctx context.Context, tx *sql.Tx) error { + // This code is executed when the migration is rolled back. + return nil +} diff --git a/model/lyrics.go b/model/lyrics.go new file mode 100644 index 00000000..4ea68d1e --- /dev/null +++ b/model/lyrics.go @@ -0,0 +1,201 @@ +package model + +import ( + "regexp" + "strconv" + "strings" + + "github.com/navidrome/navidrome/log" + "github.com/navidrome/navidrome/utils" +) + +type Line struct { + Start *int64 `structs:"start,omitempty" json:"start,omitempty"` + Value string `structs:"value" json:"value"` +} + +type Lyrics struct { + DisplayArtist string `structs:"displayArtist,omitempty" json:"displayArtist,omitempty"` + DisplayTitle string `structs:"displayTitle,omitempty" json:"displayTitle,omitempty"` + Lang string `structs:"lang" json:"lang"` + Line []Line `structs:"line" json:"line"` + Offset *int64 `structs:"offset,omitempty" json:"offset,omitempty"` + Synced bool `structs:"synced" json:"synced"` +} + +// support the standard [mm:ss.mm], as well as [hh:*] and [*.mmm] +const timeRegexString = `\[([0-9]{1,2}:)?([0-9]{1,2}):([0-9]{1,2})(.[0-9]{1,3})?\]` + +var ( + // Should either be at the beginning of file, or beginning of line + syncRegex = regexp.MustCompile(`(^|\n)\s*` + timeRegexString) + timeRegex = regexp.MustCompile(timeRegexString) + lrcIdRegex = regexp.MustCompile(`\[(ar|ti|offset):([^]]+)]`) +) + +func ToLyrics(language, text string) (*Lyrics, error) { + text = utils.SanitizeText(text) + + lines := strings.Split(text, "\n") + + artist := "" + title := "" + var offset *int64 = nil + structuredLines := []Line{} + + synced := syncRegex.MatchString(text) + priorLine := "" + validLine := false + var timestamps []int64 + + for _, line := range lines { + line := strings.TrimSpace(line) + if line == "" { + if validLine { + priorLine += "\n" + } + continue + } + var text string + var time *int64 = nil + + if synced { + idTag := lrcIdRegex.FindStringSubmatch(line) + if idTag != nil { + switch idTag[1] { + case "ar": + artist = utils.SanitizeText(strings.TrimSpace(idTag[2])) + case "offset": + { + off, err := strconv.ParseInt(strings.TrimSpace(idTag[2]), 10, 64) + if err != nil { + log.Warn("Error parsing offset", "offset", idTag[2], "error", err) + } else { + offset = &off + } + } + case "ti": + title = utils.SanitizeText(strings.TrimSpace(idTag[2])) + } + + continue + } + + times := timeRegex.FindAllStringSubmatchIndex(line, -1) + // The second condition is for when there is a timestamp in the middle of + // a line (after any text) + if times == nil || times[0][0] != 0 { + if validLine { + priorLine += "\n" + line + } + continue + } + + if validLine { + for idx := range timestamps { + structuredLines = append(structuredLines, Line{ + Start: ×tamps[idx], + Value: strings.TrimSpace(priorLine), + }) + } + timestamps = []int64{} + } + + end := 0 + + // [fullStart, fullEnd, hourStart, hourEnd, minStart, minEnd, secStart, secEnd, msStart, msEnd] + for _, match := range times { + var hours, millis int64 + var err error + + // for multiple matches, we need to check that later matches are not + // in the middle of the string + if end != 0 { + middle := strings.TrimSpace(line[end:match[0]]) + if middle != "" { + break + } + } + + end = match[1] + + hourStart := match[2] + if hourStart != -1 { + // subtract 1 because group has : at the end + hourEnd := match[3] - 1 + hours, err = strconv.ParseInt(line[hourStart:hourEnd], 10, 64) + if err != nil { + return nil, err + } + } + + minutes, err := strconv.ParseInt(line[match[4]:match[5]], 10, 64) + if err != nil { + return nil, err + } + + sec, err := strconv.ParseInt(line[match[6]:match[7]], 10, 64) + if err != nil { + return nil, err + } + + msStart := match[8] + if msStart != -1 { + msEnd := match[9] + // +1 offset since this capture group contains . + millis, err = strconv.ParseInt(line[msStart+1:msEnd], 10, 64) + if err != nil { + return nil, err + } + + length := msEnd - msStart + + if length == 3 { + millis *= 10 + } else if length == 2 { + millis *= 100 + } + } + + timeInMillis := (((((hours * 60) + minutes) * 60) + sec) * 1000) + millis + timestamps = append(timestamps, timeInMillis) + } + + if end >= len(line) { + priorLine = "" + } else { + priorLine = strings.TrimSpace(line[end:]) + } + + validLine = true + } else { + text = line + structuredLines = append(structuredLines, Line{ + Start: time, + Value: text, + }) + } + } + + if validLine { + for idx := range timestamps { + structuredLines = append(structuredLines, Line{ + Start: ×tamps[idx], + Value: strings.TrimSpace(priorLine), + }) + } + } + + lyrics := Lyrics{ + DisplayArtist: artist, + DisplayTitle: title, + Lang: language, + Line: structuredLines, + Offset: offset, + Synced: synced, + } + + return &lyrics, nil +} + +type LyricList []Lyrics diff --git a/model/lyrics_test.go b/model/lyrics_test.go new file mode 100644 index 00000000..6dedeea2 --- /dev/null +++ b/model/lyrics_test.go @@ -0,0 +1,104 @@ +package model_test + +import ( + . "github.com/navidrome/navidrome/model" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("ToLyrics", func() { + It("should parse tags with spaces", func() { + num := int64(1551) + lyrics, err := ToLyrics("xxx", "[offset: 1551 ]\n[ti: A title ]\n[ar: An artist ]\n[00:00.00]Hi there") + Expect(err).ToNot(HaveOccurred()) + Expect(lyrics.Synced).To(BeTrue()) + Expect(lyrics.DisplayArtist).To(Equal("An artist")) + Expect(lyrics.DisplayTitle).To(Equal("A title")) + Expect(lyrics.Offset).To(Equal(&num)) + }) + + It("Should ignore bad offset", func() { + lyrics, err := ToLyrics("xxx", "[offset: NotANumber ]\n[00:00.00]Hi there") + Expect(err).ToNot(HaveOccurred()) + Expect(lyrics.Offset).To(BeNil()) + }) + + It("should accept lines with no text and weird times", func() { + a, b, c, d := int64(0), int64(10040), int64(40000), int64(1000*60*60) + lyrics, err := ToLyrics("xxx", "[00:00.00]Hi there\n\n\n[00:10.040]\n[00:40]Test\n[01:00:00]late") + Expect(err).ToNot(HaveOccurred()) + Expect(lyrics.Synced).To(BeTrue()) + Expect(lyrics.Line).To(Equal([]Line{ + {Start: &a, Value: "Hi there"}, + {Start: &b, Value: ""}, + {Start: &c, Value: "Test"}, + {Start: &d, Value: "late"}, + })) + }) + + It("Should support multiple timestamps per line", func() { + a, b, c, d := int64(0), int64(10000), int64(13*60*1000), int64(1000*60*60*51) + lyrics, err := ToLyrics("xxx", "[00:00.00] [00:10.00]Repeated\n[13:00][51:00:00.00]") + Expect(err).ToNot(HaveOccurred()) + Expect(lyrics.Synced).To(BeTrue()) + Expect(lyrics.Line).To(Equal([]Line{ + {Start: &a, Value: "Repeated"}, + {Start: &b, Value: "Repeated"}, + {Start: &c, Value: ""}, + {Start: &d, Value: ""}, + })) + }) + + It("Should support parsing multiline string", func() { + a, b := int64(0), int64(10*60*1000+1) + lyrics, err := ToLyrics("xxx", "[00:00.00]This is\na multiline \n\n [:0] string\n[10:00.001]This is\nalso one") + Expect(err).ToNot(HaveOccurred()) + Expect(lyrics.Synced).To(BeTrue()) + Expect(lyrics.Line).To(Equal([]Line{ + {Start: &a, Value: "This is\na multiline\n\n[:0] string"}, + {Start: &b, Value: "This is\nalso one"}, + })) + }) + + It("Does not match timestamp in middle of line", func() { + lyrics, err := ToLyrics("xxx", "This could [00:00:00] be a synced file") + Expect(err).ToNot(HaveOccurred()) + Expect(lyrics.Synced).To(BeFalse()) + Expect(lyrics.Line).To(Equal([]Line{ + {Value: "This could [00:00:00] be a synced file"}, + })) + }) + + It("Allows timestamp in middle of line if also at beginning", func() { + a, b := int64(0), int64(1000) + lyrics, err := ToLyrics("xxx", " [00:00] This is [00:00:00] be a synced file\n [00:01]Line 2") + Expect(err).ToNot(HaveOccurred()) + Expect(lyrics.Synced).To(BeTrue()) + Expect(lyrics.Line).To(Equal([]Line{ + {Start: &a, Value: "This is [00:00:00] be a synced file"}, + {Start: &b, Value: "Line 2"}, + })) + }) + + It("Ignores lines in synchronized lyric prior to first timestamp", func() { + a := int64(0) + lyrics, err := ToLyrics("xxx", "This is some prelude\nThat doesn't\nmatter\n[00:00]Text") + Expect(err).ToNot(HaveOccurred()) + Expect(lyrics.Synced).To(BeTrue()) + Expect(lyrics.Line).To(Equal([]Line{ + {Start: &a, Value: "Text"}, + })) + }) + + It("Handles all possible ms cases", func() { + a, b, c := int64(1), int64(10), int64(100) + lyrics, err := ToLyrics("xxx", "[00:00.001]a\n[00:00.01]b\n[00:00.1]c") + Expect(err).ToNot(HaveOccurred()) + Expect(lyrics.Synced).To(BeTrue()) + Expect(lyrics.Line).To(Equal([]Line{ + {Start: &a, Value: "a"}, + {Start: &b, Value: "b"}, + {Start: &c, Value: "c"}, + })) + }) +}) diff --git a/model/mediafile.go b/model/mediafile.go index ae45af86..10b6545f 100644 --- a/model/mediafile.go +++ b/model/mediafile.go @@ -1,6 +1,7 @@ package model import ( + "encoding/json" "mime" "path/filepath" "sort" @@ -56,7 +57,7 @@ type MediaFile struct { OrderAlbumArtistName string `structs:"order_album_artist_name" json:"orderAlbumArtistName"` Compilation bool `structs:"compilation" json:"compilation"` Comment string `structs:"comment" json:"comment,omitempty"` - Lyrics string `structs:"lyrics" json:"lyrics,omitempty"` + Lyrics string `structs:"lyrics" json:"lyrics"` Bpm int `structs:"bpm" json:"bpm,omitempty"` CatalogNum string `structs:"catalog_num" json:"catalogNum,omitempty"` MbzRecordingID string `structs:"mbz_recording_id" json:"mbzRecordingID,omitempty"` @@ -92,6 +93,15 @@ func (mf MediaFile) AlbumCoverArtID() ArtworkID { return artworkIDFromAlbum(Album{ID: mf.AlbumID}) } +func (mf MediaFile) StructuredLyrics() (LyricList, error) { + lyrics := LyricList{} + err := json.Unmarshal([]byte(mf.Lyrics), &lyrics) + if err != nil { + return nil, err + } + return lyrics, nil +} + type MediaFiles []MediaFile // Dirs returns a deduped list of all directories from the MediaFiles' paths diff --git a/scanner/mapping.go b/scanner/mapping.go index c9e255cb..f59ea9cc 100644 --- a/scanner/mapping.go +++ b/scanner/mapping.go @@ -73,7 +73,7 @@ func (s MediaFileMapper) ToMediaFile(md metadata.Tags) model.MediaFile { mf.RGTrackGain = md.RGTrackGain() mf.RGTrackPeak = md.RGTrackPeak() mf.Comment = utils.SanitizeText(md.Comment()) - mf.Lyrics = utils.SanitizeText(md.Lyrics()) + mf.Lyrics = md.Lyrics() mf.Bpm = md.Bpm() mf.CreatedAt = md.BirthTime() mf.UpdatedAt = md.ModificationTime() diff --git a/scanner/metadata/ffmpeg/ffmpeg_test.go b/scanner/metadata/ffmpeg/ffmpeg_test.go index 7f1c63bf..370c018e 100644 --- a/scanner/metadata/ffmpeg/ffmpeg_test.go +++ b/scanner/metadata/ffmpeg/ffmpeg_test.go @@ -316,4 +316,35 @@ Input #0, mp3, from '/Users/deluan/Music/Music/Media/_/Wyclef Jean - From the Hu Expect(md).To(HaveKeyWithValue("replaygain_album_peak", []string{"0.9125"})) }) + + It("parses lyrics with language code", func() { + const output = ` + Input #0, mp3, from 'test.mp3': + Metadata: + lyrics-eng : [00:00.00]This is + : [00:02.50]English + lyrics-xxx : [00:00.00]This is + : [00:02.50]unspecified + ` + md, _ := e.extractMetadata("tests/fixtures/test.mp3", output) + Expect(md).To(HaveKeyWithValue("lyrics-eng", []string{ + "[00:00.00]This is\n[00:02.50]English", + })) + Expect(md).To(HaveKeyWithValue("lyrics-xxx", []string{ + "[00:00.00]This is\n[00:02.50]unspecified", + })) + }) + + It("parses normal LYRICS tag", func() { + const output = ` + Input #0, mp3, from 'test.mp3': + Metadata: + LYRICS : [00:00.00]This is + : [00:02.50]English + ` + md, _ := e.extractMetadata("tests/fixtures/test.mp3", output) + Expect(md).To(HaveKeyWithValue("lyrics", []string{ + "[00:00.00]This is\n[00:02.50]English", + })) + }) }) diff --git a/scanner/metadata/metadata.go b/scanner/metadata/metadata.go index cb3d18ff..5b0f92b0 100644 --- a/scanner/metadata/metadata.go +++ b/scanner/metadata/metadata.go @@ -1,6 +1,7 @@ package metadata import ( + "encoding/json" "fmt" "math" "os" @@ -15,6 +16,7 @@ import ( "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/log" + "github.com/navidrome/navidrome/model" ) type Extractor interface { @@ -131,8 +133,47 @@ func (t Tags) OriginalDate() (int, string) { return t.getDate("originaldate") } func (t Tags) ReleaseDate() (int, string) { return t.getDate("releasedate") } func (t Tags) Comment() string { return t.getFirstTagValue("comment") } func (t Tags) Lyrics() string { - return t.getFirstTagValue("lyrics", "lyrics-eng", "unsynced_lyrics", "unsynced lyrics", "unsyncedlyrics") + lyricList := model.LyricList{} + basicLyrics := t.getAllTagValues("lyrics", "unsynced_lyrics", "unsynced lyrics", "unsyncedlyrics") + + for _, value := range basicLyrics { + lyrics, err := model.ToLyrics("xxx", value) + if err != nil { + log.Warn("Unexpected failure occurred when parsing lyrics", "file", t.filePath, "error", err) + continue + } + + lyricList = append(lyricList, *lyrics) + } + + for tag, value := range t.Tags { + if strings.HasPrefix(tag, "lyrics-") { + language := strings.TrimSpace(strings.TrimPrefix(tag, "lyrics-")) + + if language == "" { + language = "xxx" + } + + for _, text := range value { + lyrics, err := model.ToLyrics(language, text) + if err != nil { + log.Warn("Unexpected failure occurred when parsing lyrics", "file", t.filePath, "error", err) + continue + } + + lyricList = append(lyricList, *lyrics) + } + } + } + + res, err := json.Marshal(lyricList) + if err != nil { + log.Warn("Unexpected error occurred when serializing lyrics", "file", t.filePath, "error", err) + return "" + } + return string(res) } + func (t Tags) Compilation() bool { return t.getBool("tcmp", "compilation", "wm/iscompilation") } func (t Tags) TrackNumber() (int, int) { return t.getTuple("track", "tracknumber") } func (t Tags) DiscNumber() (int, int) { return t.getTuple("disc", "discnumber") } diff --git a/scanner/metadata/metadata_test.go b/scanner/metadata/metadata_test.go index be275500..9ae422bc 100644 --- a/scanner/metadata/metadata_test.go +++ b/scanner/metadata/metadata_test.go @@ -1,15 +1,64 @@ package metadata_test import ( + "encoding/json" + "strings" + "github.com/navidrome/navidrome/conf" + "github.com/navidrome/navidrome/conf/configtest" + "github.com/navidrome/navidrome/core/ffmpeg" + "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/scanner/metadata" _ "github.com/navidrome/navidrome/scanner/metadata/ffmpeg" _ "github.com/navidrome/navidrome/scanner/metadata/taglib" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "golang.org/x/exp/slices" ) var _ = Describe("Tags", func() { + var zero int64 = 0 + var secondTs int64 = 2500 + + makeLyrics := func(synced bool, lang, secondLine string) model.Lyrics { + lines := []model.Line{ + {Value: "This is"}, + {Value: secondLine}, + } + + if synced { + lines[0].Start = &zero + lines[1].Start = &secondTs + } + + lyrics := model.Lyrics{ + Lang: lang, + Line: lines, + Synced: synced, + } + + return lyrics + } + + sortLyrics := func(lines model.LyricList) model.LyricList { + slices.SortFunc(lines, func(a, b model.Lyrics) bool { + langDiff := strings.Compare(a.Lang, b.Lang) + if langDiff == 0 { + return strings.Compare(a.Line[1].Value, b.Line[1].Value) < 0 + } else { + return langDiff < 0 + } + }) + + return lines + } + + compareLyrics := func(m metadata.Tags, expected model.LyricList) { + lyrics := model.LyricList{} + Expect(json.Unmarshal([]byte(m.Lyrics()), &lyrics)).To(BeNil()) + Expect(sortLyrics(lyrics)).To(Equal(sortLyrics(expected))) + } + Context("Extract", func() { BeforeEach(func() { conf.Server.Scanner.Extractor = "taglib" @@ -61,10 +110,10 @@ var _ = Describe("Tags", func() { Expect(m.Duration()).To(BeNumerically("~", 1.04, 0.01)) Expect(m.Suffix()).To(Equal("ogg")) Expect(m.FilePath()).To(Equal("tests/fixtures/test.ogg")) - Expect(m.Size()).To(Equal(int64(6333))) + Expect(m.Size()).To(Equal(int64(5534))) // TabLib 1.12 returns 18, previous versions return 39. // See https://github.com/taglib/taglib/commit/2f238921824741b2cfe6fbfbfc9701d9827ab06b - Expect(m.BitRate()).To(BeElementOf(18, 39, 40, 49)) + Expect(m.BitRate()).To(BeElementOf(18, 39, 40, 43, 49)) m = mds["tests/fixtures/test.wma"] Expect(err).To(BeNil()) @@ -74,8 +123,86 @@ var _ = Describe("Tags", func() { Expect(m.Duration()).To(BeNumerically("~", 1.02, 0.01)) Expect(m.Suffix()).To(Equal("wma")) Expect(m.FilePath()).To(Equal("tests/fixtures/test.wma")) - Expect(m.Size()).To(Equal(int64(21431))) + Expect(m.Size()).To(Equal(int64(21581))) Expect(m.BitRate()).To(BeElementOf(128)) }) + + DescribeTable("Lyrics test", + func(file string, langEncoded bool) { + path := "tests/fixtures/" + file + mds, err := metadata.Extract(path) + Expect(err).ToNot(HaveOccurred()) + Expect(mds).To(HaveLen(1)) + + m := mds[path] + lyrics := model.LyricList{ + makeLyrics(true, "xxx", "English"), + makeLyrics(true, "xxx", "unspecified"), + } + if langEncoded { + lyrics[0].Lang = "eng" + } + compareLyrics(m, lyrics) + }, + + Entry("Parses AIFF file", "test.aiff", true), + Entry("Parses FLAC files", "test.flac", false), + Entry("Parses M4A files", "01 Invisible (RED) Edit Version.m4a", false), + Entry("Parses OGG Vorbis files", "test.ogg", false), + Entry("Parses WAV files", "test.wav", true), + Entry("Parses WMA files", "test.wma", false), + Entry("Parses WV files", "test.wv", false), + ) + + It("Should parse mp3 with USLT and SYLT", func() { + path := "tests/fixtures/test.mp3" + mds, err := metadata.Extract(path) + Expect(err).ToNot(HaveOccurred()) + Expect(mds).To(HaveLen(1)) + + m := mds[path] + compareLyrics(m, model.LyricList{ + makeLyrics(true, "eng", "English SYLT"), + makeLyrics(true, "eng", "English"), + makeLyrics(true, "xxx", "unspecified SYLT"), + makeLyrics(true, "xxx", "unspecified"), + }) + }) + }) + + // Only run these tests if FFmpeg is available + FFmpegContext := XContext + if ffmpeg.New().IsAvailable() { + FFmpegContext = Context + } + FFmpegContext("Extract with FFmpeg", func() { + BeforeEach(func() { + DeferCleanup(configtest.SetupConfig()) + conf.Server.Scanner.Extractor = "ffmpeg" + }) + + DescribeTable("Lyrics test", + func(file string) { + path := "tests/fixtures/" + file + mds, err := metadata.Extract(path) + Expect(err).ToNot(HaveOccurred()) + Expect(mds).To(HaveLen(1)) + + m := mds[path] + compareLyrics(m, model.LyricList{ + makeLyrics(true, "eng", "English"), + makeLyrics(true, "xxx", "unspecified"), + }) + }, + + Entry("Parses AIFF file", "test.aiff"), + Entry("Parses MP3 files", "test.mp3"), + // Disabled, because it fails in pipeline + // Entry("Parses WAV files", "test.wav"), + + // FFMPEG behaves very weirdly for multivalued tags for non-ID3 + // Specifically, they are separated by ";, which is indistinguishable + // from other fields + ) }) }) diff --git a/scanner/metadata/taglib/taglib_test.go b/scanner/metadata/taglib/taglib_test.go index ed4b5034..e499ab18 100644 --- a/scanner/metadata/taglib/taglib_test.go +++ b/scanner/metadata/taglib/taglib_test.go @@ -39,7 +39,10 @@ var _ = Describe("Extractor", func() { Expect(m).To(HaveKeyWithValue("album", []string{"Album", "Album"})) Expect(m).To(HaveKeyWithValue("artist", []string{"Artist", "Artist"})) Expect(m).To(HaveKeyWithValue("albumartist", []string{"Album Artist"})) - Expect(m).To(HaveKeyWithValue("tcmp", []string{"1"})) // Compilation + + Expect(m).To(Or( + HaveKeyWithValue("compilation", []string{"1"}), + HaveKeyWithValue("tcmp", []string{"1"}))) // Compilation Expect(m).To(HaveKeyWithValue("genre", []string{"Rock"})) Expect(m).To(HaveKeyWithValue("date", []string{"2014-05-21", "2014"})) Expect(m).To(HaveKeyWithValue("originaldate", []string{"1996-11-21"})) @@ -50,7 +53,21 @@ var _ = Describe("Extractor", func() { Expect(m).To(HaveKeyWithValue("bitrate", []string{"192"})) Expect(m).To(HaveKeyWithValue("channels", []string{"2"})) Expect(m).To(HaveKeyWithValue("comment", []string{"Comment1\nComment2"})) - Expect(m).To(HaveKeyWithValue("lyrics", []string{"Lyrics 1\rLyrics 2"})) + Expect(m).ToNot(HaveKey("lyrics")) + Expect(m).To(Or(HaveKeyWithValue("lyrics-eng", []string{ + "[00:00.00]This is\n[00:02.50]English SYLT\n", + "[00:00.00]This is\n[00:02.50]English", + }), HaveKeyWithValue("lyrics-eng", []string{ + "[00:00.00]This is\n[00:02.50]English", + "[00:00.00]This is\n[00:02.50]English SYLT\n", + }))) + Expect(m).To(Or(HaveKeyWithValue("lyrics-xxx", []string{ + "[00:00.00]This is\n[00:02.50]unspecified SYLT\n", + "[00:00.00]This is\n[00:02.50]unspecified", + }), HaveKeyWithValue("lyrics-xxx", []string{ + "[00:00.00]This is\n[00:02.50]unspecified", + "[00:00.00]This is\n[00:02.50]unspecified SYLT\n", + }))) Expect(m).To(HaveKeyWithValue("bpm", []string{"123"})) Expect(m).To(HaveKeyWithValue("replaygain_album_gain", []string{"+3.21518 dB"})) Expect(m).To(HaveKeyWithValue("replaygain_album_peak", []string{"0.9125"})) @@ -70,10 +87,10 @@ var _ = Describe("Extractor", func() { // TabLib 1.12 returns 18, previous versions return 39. // See https://github.com/taglib/taglib/commit/2f238921824741b2cfe6fbfbfc9701d9827ab06b Expect(m).To(HaveKey("bitrate")) - Expect(m["bitrate"][0]).To(BeElementOf("18", "39", "40", "49")) + Expect(m["bitrate"][0]).To(BeElementOf("18", "39", "40", "43", "49")) }) DescribeTable("Format-Specific tests", - func(file, duration, channels, albumGain, albumPeak, trackGain, trackPeak string) { + func(file, duration, channels, albumGain, albumPeak, trackGain, trackPeak string, id3Lyrics bool) { file = "tests/fixtures/" + file mds, err := e.Parse(file) Expect(err).NotTo(HaveOccurred()) @@ -113,7 +130,21 @@ var _ = Describe("Extractor", func() { Expect(m).To(HaveKeyWithValue("channels", []string{channels})) Expect(m).To(HaveKeyWithValue("comment", []string{"Comment1\nComment2"})) - Expect(m).To(HaveKeyWithValue("lyrics", []string{"Lyrics1\nLyrics 2"})) + + if id3Lyrics { + Expect(m).To(HaveKeyWithValue("lyrics-eng", []string{ + "[00:00.00]This is\n[00:02.50]English", + })) + Expect(m).To(HaveKeyWithValue("lyrics-xxx", []string{ + "[00:00.00]This is\n[00:02.50]unspecified", + })) + } else { + Expect(m).To(HaveKeyWithValue("lyrics", []string{ + "[00:00.00]This is\n[00:02.50]unspecified", + "[00:00.00]This is\n[00:02.50]English", + })) + } + Expect(m).To(HaveKeyWithValue("bpm", []string{"123"})) Expect(m).To(HaveKey("tracknumber")) @@ -123,25 +154,26 @@ var _ = Describe("Extractor", func() { }, // ffmpeg -f lavfi -i "sine=frequency=1200:duration=1" test.flac - Entry("correctly parses flac tags", "test.flac", "1.00", "1", "+4.06 dB", "0.12496948", "+4.06 dB", "0.12496948"), + Entry("correctly parses flac tags", "test.flac", "1.00", "1", "+4.06 dB", "0.12496948", "+4.06 dB", "0.12496948", false), - Entry("Correctly parses m4a (aac) gain tags", "01 Invisible (RED) Edit Version.m4a", "1.04", "2", "0.37", "0.48", "0.37", "0.48"), - Entry("Correctly parses m4a (aac) gain tags (uppercase)", "test.m4a", "1.04", "2", "0.37", "0.48", "0.37", "0.48"), + Entry("Correctly parses m4a (aac) gain tags", "01 Invisible (RED) Edit Version.m4a", "1.04", "2", "0.37", "0.48", "0.37", "0.48", false), + Entry("Correctly parses m4a (aac) gain tags (uppercase)", "test.m4a", "1.04", "2", "0.37", "0.48", "0.37", "0.48", false), - Entry("correctly parses ogg (vorbis) tags", "test.ogg", "1.04", "2", "+7.64 dB", "0.11772506", "+7.64 dB", "0.11772506"), + Entry("correctly parses ogg (vorbis) tags", "test.ogg", "1.04", "2", "+7.64 dB", "0.11772506", "+7.64 dB", "0.11772506", false), // ffmpeg -f lavfi -i "sine=frequency=900:duration=1" test.wma - Entry("correctly parses wma/asf tags", "test.wma", "1.02", "1", "3.27 dB", "0.132914", "3.27 dB", "0.132914"), + // Weird note: for the tag parsing to work, the lyrics are actually stored in the reverse order + Entry("correctly parses wma/asf tags", "test.wma", "1.02", "1", "3.27 dB", "0.132914", "3.27 dB", "0.132914", false), // ffmpeg -f lavfi -i "sine=frequency=800:duration=1" test.wv - Entry("correctly parses wv (wavpak) tags", "test.wv", "1.00", "1", "3.43 dB", "0.125061", "3.43 dB", "0.125061"), + Entry("correctly parses wv (wavpak) tags", "test.wv", "1.00", "1", "3.43 dB", "0.125061", "3.43 dB", "0.125061", false), // TODO - these breaks in the pipeline as it uses TabLib 1.11. Once Ubuntu 24.04 is released we can uncomment these tests // ffmpeg -f lavfi -i "sine=frequency=1000:duration=1" test.wav - //Entry("correctly parses wav tags", "test.wav", "1.00", "1", "3.06 dB", "0.125056", "3.06 dB", "0.125056"), + // Entry("correctly parses wav tags", "test.wav", "1.00", "1", "3.06 dB", "0.125056", "3.06 dB", "0.125056", true), // ffmpeg -f lavfi -i "sine=frequency=1400:duration=1" test.aiff - //Entry("correctly parses aiff tags", "test.aiff", "1.00", "1", "2.00 dB", "0.124972", "2.00 dB", "0.124972"), + // Entry("correctly parses aiff tags", "test.aiff", "1.00", "1", "2.00 dB", "0.124972", "2.00 dB", "0.124972", true), ) }) @@ -155,6 +187,12 @@ var _ = Describe("Extractor", func() { _, err := e.extractMetadata(testFilePath) Expect(err).To(MatchError(fs.ErrNotExist)) }) + It("does not throw a SIGSEGV error when reading a file with an invalid frame", func() { + // File has an empty TDAT frame + md, err := e.extractMetadata("tests/fixtures/invalid-files/test-invalid-frame.mp3") + Expect(err).ToNot(HaveOccurred()) + Expect(md).To(HaveKeyWithValue("albumartist", []string{"Elvis Presley"})) + }) }) }) diff --git a/scanner/metadata/taglib/taglib_wrapper.cpp b/scanner/metadata/taglib/taglib_wrapper.cpp index 6cc77f56..1aceecca 100644 --- a/scanner/metadata/taglib/taglib_wrapper.cpp +++ b/scanner/metadata/taglib/taglib_wrapper.cpp @@ -3,15 +3,19 @@ #include #define TAGLIB_STATIC +#include #include #include #include #include +#include +#include #include #include #include #include #include +#include #include "taglib_wrapper.h" @@ -58,15 +62,86 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { } } + TagLib::ID3v2::Tag *id3Tags = NULL; + // Get some extended/non-standard ID3-only tags (ex: iTunes extended frames) TagLib::MPEG::File *mp3File(dynamic_cast(f.file())); if (mp3File != NULL) { - if (mp3File->ID3v2Tag()) { - const auto &frameListMap(mp3File->ID3v2Tag()->frameListMap()); + id3Tags = mp3File->ID3v2Tag(); + } - for (const auto &kv : frameListMap) { - if (!kv.second.isEmpty()) + if (id3Tags == NULL) { + TagLib::RIFF::WAV::File *wavFile(dynamic_cast(f.file())); + if (wavFile != NULL && wavFile->hasID3v2Tag()) { + id3Tags = wavFile->ID3v2Tag(); + } + } + + if (id3Tags == NULL) { + TagLib::RIFF::AIFF::File *aiffFile(dynamic_cast(f.file())); + if (aiffFile && aiffFile->hasID3v2Tag()) { + id3Tags = aiffFile->tag(); + } + } + + // Yes, it is possible to have ID3v2 tags in FLAC. However, that can cause problems + // with many players, so they will not be parsed + + if (id3Tags != NULL) { + const auto &frames = id3Tags->frameListMap(); + + for (const auto &kv: frames) { + if (kv.first == "USLT") { + for (const auto &tag: kv.second) { + TagLib::ID3v2::UnsynchronizedLyricsFrame *frame = dynamic_cast(tag); + if (frame == NULL) continue; + + tags.erase("LYRICS"); + + const auto bv = frame->language(); + char language[4] = {'x', 'x', 'x', '\0'}; + if (bv.size() == 3) { + strncpy(language, bv.data(), 3); + } + + char *val = (char *)frame->text().toCString(true); + + go_map_put_lyrics(id, language, val); + } + } else if (kv.first == "SYLT") { + for (const auto &tag: kv.second) { + TagLib::ID3v2::SynchronizedLyricsFrame *frame = dynamic_cast(tag); + if (frame == NULL) continue; + + const auto bv = frame->language(); + char language[4] = {'x', 'x', 'x', '\0'}; + if (bv.size() == 3) { + strncpy(language, bv.data(), 3); + } + + const auto format = frame->timestampFormat(); + if (format == TagLib::ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds) { + + for (const auto &line: frame->synchedText()) { + char *text = (char *)line.text.toCString(true); + go_map_put_lyric_line(id, language, text, line.time); + } + } else if (format == TagLib::ID3v2::SynchronizedLyricsFrame::AbsoluteMpegFrames) { + const int sampleRate = props->sampleRate(); + + if (sampleRate != 0) { + for (const auto &line: frame->synchedText()) { + const int timeInMs = (line.time * 1000) / sampleRate; + char *text = (char *)line.text.toCString(true); + go_map_put_lyric_line(id, language, text, timeInMs); + } + } + } + } + } else { + if (!kv.second.isEmpty()) { tags.insert(kv.first, kv.second.front()->toString()); + } } } } @@ -90,7 +165,7 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { const TagLib::ASF::Tag *asfTags{asfFile->tag()}; const auto itemListMap = asfTags->attributeListMap(); for (const auto item : itemListMap) { - tags.insert(item.first, item.second.front().toString()); + tags.insert(item.first, item.second.front().toString()); } } diff --git a/scanner/metadata/taglib/taglib_wrapper.go b/scanner/metadata/taglib/taglib_wrapper.go index c631d0b2..ec196ab6 100644 --- a/scanner/metadata/taglib/taglib_wrapper.go +++ b/scanner/metadata/taglib/taglib_wrapper.go @@ -103,6 +103,12 @@ func go_map_put_str(id C.ulong, key *C.char, val *C.char) { do_put_map(id, k, val) } +//export go_map_put_lyrics +func go_map_put_lyrics(id C.ulong, lang *C.char, val *C.char) { + k := "lyrics-" + strings.ToLower(C.GoString(lang)) + do_put_map(id, k, val) +} + func do_put_map(id C.ulong, key string, val *C.char) { if key == "" { return @@ -126,3 +132,30 @@ func go_map_put_int(id C.ulong, key *C.char, val C.int) { defer C.free(unsafe.Pointer(vp)) go_map_put_str(id, key, vp) } + +//export go_map_put_lyric_line +func go_map_put_lyric_line(id C.ulong, lang *C.char, text *C.char, time C.int) { + language := C.GoString(lang) + line := C.GoString(text) + timeGo := int64(time) + + ms := timeGo % 1000 + timeGo /= 1000 + sec := timeGo % 60 + timeGo /= 60 + min := timeGo % 60 + formatted_line := fmt.Sprintf("[%02d:%02d.%02d]%s\n", min, sec, ms/10, line) + + lock.RLock() + defer lock.RUnlock() + + key := "lyrics-" + language + + m := maps[uint32(id)] + existing, ok := m[key] + if ok { + existing[0] += formatted_line + } else { + m[key] = []string{formatted_line} + } +} diff --git a/scanner/metadata/taglib/taglib_wrapper.h b/scanner/metadata/taglib/taglib_wrapper.h index 5625d2fa..8f0a8212 100644 --- a/scanner/metadata/taglib/taglib_wrapper.h +++ b/scanner/metadata/taglib/taglib_wrapper.h @@ -14,6 +14,8 @@ extern "C" { extern void go_map_put_m4a_str(unsigned long id, char *key, char *val); extern void go_map_put_str(unsigned long id, char *key, char *val); extern void go_map_put_int(unsigned long id, char *key, int val); +extern void go_map_put_lyrics(unsigned long id, char *lang, char *val); +extern void go_map_put_lyric_line(unsigned long id, char *lang, char *text, int time); int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id); #ifdef __cplusplus diff --git a/server/subsonic/api.go b/server/subsonic/api.go index 63064b30..0f8c909f 100644 --- a/server/subsonic/api.go +++ b/server/subsonic/api.go @@ -142,6 +142,7 @@ func (api *Router) routes() http.Handler { r.Group(func(r chi.Router) { hr(r, "getAvatar", api.GetAvatar) h(r, "getLyrics", api.GetLyrics) + h(r, "getLyricsBySongId", api.GetLyricsBySongId) }) r.Group(func(r chi.Router) { // configure request throttling diff --git a/server/subsonic/helpers.go b/server/subsonic/helpers.go index 46d1ab31..78886d01 100644 --- a/server/subsonic/helpers.go +++ b/server/subsonic/helpers.go @@ -323,3 +323,45 @@ func buildAlbumID3(ctx context.Context, album model.Album) responses.AlbumID3 { dir.OriginalReleaseDate = toItemDate(album.OriginalDate) return dir } + +func buildStructuredLyric(mf *model.MediaFile, lyrics model.Lyrics) responses.StructuredLyric { + lines := make([]responses.Line, len(lyrics.Line)) + + for i, line := range lyrics.Line { + lines[i] = responses.Line{ + Start: line.Start, + Value: line.Value, + } + } + + structured := responses.StructuredLyric{ + DisplayArtist: lyrics.DisplayArtist, + DisplayTitle: lyrics.DisplayTitle, + Lang: lyrics.Lang, + Line: lines, + Offset: lyrics.Offset, + Synced: lyrics.Synced, + } + + if structured.DisplayArtist == "" { + structured.DisplayArtist = mf.Artist + } + if structured.DisplayTitle == "" { + structured.DisplayTitle = mf.Title + } + + return structured +} + +func buildLyricsList(mf *model.MediaFile, lyricsList model.LyricList) *responses.LyricsList { + lyricList := make(responses.StructuredLyrics, len(lyricsList)) + + for i, lyrics := range lyricsList { + lyricList[i] = buildStructuredLyric(mf, lyrics) + } + + res := &responses.LyricsList{ + StructuredLyrics: lyricList, + } + return res +} diff --git a/server/subsonic/media_retrieval.go b/server/subsonic/media_retrieval.go index 5d4383d6..07b91730 100644 --- a/server/subsonic/media_retrieval.go +++ b/server/subsonic/media_retrieval.go @@ -5,7 +5,6 @@ import ( "errors" "io" "net/http" - "regexp" "time" "github.com/navidrome/navidrome/conf" @@ -90,16 +89,6 @@ func (api *Router) GetCoverArt(w http.ResponseWriter, r *http.Request) (*respons return nil, err } -const timeStampRegex string = `(\[([0-9]{1,2}:)?([0-9]{1,2}:)([0-9]{1,2})(\.[0-9]{1,2})?\])` - -func isSynced(rawLyrics string) bool { - r := regexp.MustCompile(timeStampRegex) - // Eg: [04:02:50.85] - // [02:50.85] - // [02:50] - return r.MatchString(rawLyrics) -} - func (api *Router) GetLyrics(r *http.Request) (*responses.Subsonic, error) { p := req.Params(r) artist, _ := p.String("artist") @@ -117,15 +106,46 @@ func (api *Router) GetLyrics(r *http.Request) (*responses.Subsonic, error) { return response, nil } + structuredLyrics, err := mediaFiles[0].StructuredLyrics() + if err != nil { + return nil, err + } + + if len(structuredLyrics) == 0 { + return response, nil + } + lyrics.Artist = artist lyrics.Title = title - if isSynced(mediaFiles[0].Lyrics) { - r := regexp.MustCompile(timeStampRegex) - lyrics.Value = r.ReplaceAllString(mediaFiles[0].Lyrics, "") - } else { - lyrics.Value = mediaFiles[0].Lyrics + lyricsText := "" + for _, line := range structuredLyrics[0].Line { + lyricsText += line.Value + "\n" } + lyrics.Value = lyricsText + + return response, nil +} + +func (api *Router) GetLyricsBySongId(r *http.Request) (*responses.Subsonic, error) { + id, err := req.Params(r).String("id") + if err != nil { + return nil, err + } + + mediaFile, err := api.ds.MediaFile(r.Context()).Get(id) + if err != nil { + return nil, err + } + + lyrics, err := mediaFile.StructuredLyrics() + if err != nil { + return nil, err + } + + response := newResponse() + response.LyricsList = buildLyricsList(mediaFile, lyrics) + return response, nil } diff --git a/server/subsonic/media_retrieval_test.go b/server/subsonic/media_retrieval_test.go index 5246fd61..12e32dad 100644 --- a/server/subsonic/media_retrieval_test.go +++ b/server/subsonic/media_retrieval_test.go @@ -3,6 +3,7 @@ package subsonic import ( "bytes" "context" + "encoding/json" "errors" "io" "net/http/httptest" @@ -11,6 +12,7 @@ import ( "github.com/navidrome/navidrome/core/artwork" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" + "github.com/navidrome/navidrome/server/subsonic/responses" "github.com/navidrome/navidrome/tests" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -72,12 +74,18 @@ var _ = Describe("MediaRetrievalController", func() { Describe("GetLyrics", func() { It("should return data for given artist & title", func() { r := newGetRequest("artist=Rick+Astley", "title=Never+Gonna+Give+You+Up") + lyrics, _ := model.ToLyrics("eng", "[00:18.80]We're no strangers to love\n[00:22.80]You know the rules and so do I") + lyricsJson, err := json.Marshal(model.LyricList{ + *lyrics, + }) + Expect(err).ToNot(HaveOccurred()) + mockRepo.SetData(model.MediaFiles{ { ID: "1", Artist: "Rick Astley", Title: "Never Gonna Give You Up", - Lyrics: "[00:18.80]We're no strangers to love\n[00:22.80]You know the rules and so do I", + Lyrics: string(lyricsJson), }, }) response, err := router.GetLyrics(r) @@ -87,7 +95,7 @@ var _ = Describe("MediaRetrievalController", func() { Expect(err).To(BeNil()) Expect(response.Lyrics.Artist).To(Equal("Rick Astley")) Expect(response.Lyrics.Title).To(Equal("Never Gonna Give You Up")) - Expect(response.Lyrics.Value).To(Equal("We're no strangers to love\nYou know the rules and so do I")) + Expect(response.Lyrics.Value).To(Equal("We're no strangers to love\nYou know the rules and so do I\n")) }) It("should return empty subsonic response if the record corresponding to the given artist & title is not found", func() { r := newGetRequest("artist=Dheeraj", "title=Rinkiya+Ke+Papa") @@ -100,7 +108,143 @@ var _ = Describe("MediaRetrievalController", func() { Expect(response.Lyrics.Artist).To(Equal("")) Expect(response.Lyrics.Title).To(Equal("")) Expect(response.Lyrics.Value).To(Equal("")) + }) + }) + Describe("getLyricsBySongId", func() { + const syncedLyrics = "[00:18.80]We're no strangers to love\n[00:22.801]You know the rules and so do I" + const unsyncedLyrics = "We're no strangers to love\nYou know the rules and so do I" + const metadata = "[ar:Rick Astley]\n[ti:That one song]\n[offset:-100]" + var times = []int64{18800, 22801} + + compareResponses := func(actual *responses.LyricsList, expected responses.LyricsList) { + Expect(actual).ToNot(BeNil()) + Expect(actual.StructuredLyrics).To(HaveLen(len(expected.StructuredLyrics))) + for i, realLyric := range actual.StructuredLyrics { + expectedLyric := expected.StructuredLyrics[i] + + Expect(realLyric.DisplayArtist).To(Equal(expectedLyric.DisplayArtist)) + Expect(realLyric.DisplayTitle).To(Equal(expectedLyric.DisplayTitle)) + Expect(realLyric.Lang).To(Equal(expectedLyric.Lang)) + Expect(realLyric.Synced).To(Equal(expectedLyric.Synced)) + + if expectedLyric.Offset == nil { + Expect(realLyric.Offset).To(BeNil()) + } else { + Expect(*realLyric.Offset).To(Equal(*expectedLyric.Offset)) + } + + Expect(realLyric.Line).To(HaveLen(len(expectedLyric.Line))) + for j, realLine := range realLyric.Line { + expectedLine := expectedLyric.Line[j] + Expect(realLine.Value).To(Equal(expectedLine.Value)) + + if expectedLine.Start == nil { + Expect(realLine.Start).To(BeNil()) + } else { + Expect(*realLine.Start).To(Equal(*expectedLine.Start)) + } + } + } + } + + It("should return mixed lyrics", func() { + r := newGetRequest("id=1") + synced, _ := model.ToLyrics("eng", syncedLyrics) + unsynced, _ := model.ToLyrics("xxx", unsyncedLyrics) + lyricsJson, err := json.Marshal(model.LyricList{ + *synced, *unsynced, + }) + Expect(err).ToNot(HaveOccurred()) + + mockRepo.SetData(model.MediaFiles{ + { + ID: "1", + Artist: "Rick Astley", + Title: "Never Gonna Give You Up", + Lyrics: string(lyricsJson), + }, + }) + + response, err := router.GetLyricsBySongId(r) + Expect(err).ToNot(HaveOccurred()) + compareResponses(response.LyricsList, responses.LyricsList{ + StructuredLyrics: responses.StructuredLyrics{ + { + Lang: "eng", + DisplayArtist: "Rick Astley", + DisplayTitle: "Never Gonna Give You Up", + Synced: true, + Line: []responses.Line{ + { + Start: ×[0], + Value: "We're no strangers to love", + }, + { + Start: ×[1], + Value: "You know the rules and so do I", + }, + }, + }, + { + Lang: "xxx", + DisplayArtist: "Rick Astley", + DisplayTitle: "Never Gonna Give You Up", + Synced: false, + Line: []responses.Line{ + { + Value: "We're no strangers to love", + }, + { + Value: "You know the rules and so do I", + }, + }, + }, + }, + }) + }) + + It("should parse lrc metadata", func() { + r := newGetRequest("id=1") + synced, _ := model.ToLyrics("eng", metadata+"\n"+syncedLyrics) + lyricsJson, err := json.Marshal(model.LyricList{ + *synced, + }) + Expect(err).ToNot(HaveOccurred()) + mockRepo.SetData(model.MediaFiles{ + { + ID: "1", + Artist: "Rick Astley", + Title: "Never Gonna Give You Up", + Lyrics: string(lyricsJson), + }, + }) + + response, err := router.GetLyricsBySongId(r) + Expect(err).ToNot(HaveOccurred()) + + offset := int64(-100) + compareResponses(response.LyricsList, responses.LyricsList{ + StructuredLyrics: responses.StructuredLyrics{ + { + DisplayArtist: "Rick Astley", + DisplayTitle: "That one song", + Lang: "eng", + Synced: true, + Line: []responses.Line{ + { + Start: ×[0], + Value: "We're no strangers to love", + }, + { + Start: ×[1], + Value: "You know the rules and so do I", + }, + }, + Offset: &offset, + }, + }, + }) }) }) }) @@ -122,26 +266,6 @@ func (c *fakeArtwork) GetOrPlaceholder(_ context.Context, id string, size int) ( return io.NopCloser(bytes.NewReader([]byte(c.data))), time.Time{}, nil } -var _ = Describe("isSynced", func() { - It("returns false if lyrics contain no timestamps", func() { - Expect(isSynced("Just in case my car goes off the highway")).To(Equal(false)) - Expect(isSynced("[02.50] Just in case my car goes off the highway")).To(Equal(false)) - }) - It("returns false if lyrics is an empty string", func() { - Expect(isSynced("")).To(Equal(false)) - }) - It("returns true if lyrics contain timestamps", func() { - Expect(isSynced(`NF Real Music - [00:00] First line - [00:00.85] JUST LIKE YOU - [00:00.85] Just in case my car goes off the highway`)).To(Equal(true)) - Expect(isSynced("[04:02:50.85] Never gonna give you up")).To(Equal(true)) - Expect(isSynced("[02:50.85] Never gonna give you up")).To(Equal(true)) - Expect(isSynced("[02:50] Never gonna give you up")).To(Equal(true)) - }) - -}) - type mockedMediaFile struct { model.MediaFileRepository data model.MediaFiles @@ -154,3 +278,12 @@ func (m *mockedMediaFile) SetData(mfs model.MediaFiles) { func (m *mockedMediaFile) GetAll(...model.QueryOptions) (model.MediaFiles, error) { return m.data, nil } + +func (m *mockedMediaFile) Get(id string) (*model.MediaFile, error) { + for _, mf := range m.data { + if mf.ID == id { + return &mf, nil + } + } + return nil, model.ErrNotFound +} diff --git a/server/subsonic/opensubsonic.go b/server/subsonic/opensubsonic.go index 126f9613..17ce3c2b 100644 --- a/server/subsonic/opensubsonic.go +++ b/server/subsonic/opensubsonic.go @@ -11,6 +11,7 @@ func (api *Router) GetOpenSubsonicExtensions(_ *http.Request) (*responses.Subson response.OpenSubsonicExtensions = &responses.OpenSubsonicExtensions{ {Name: "transcodeOffset", Versions: []int32{1}}, {Name: "formPost", Versions: []int32{1}}, + {Name: "songLyrics", Versions: []int32{1}}, } return response, nil } diff --git a/server/subsonic/responses/.snapshots/Responses LyricsList with data should match .JSON b/server/subsonic/responses/.snapshots/Responses LyricsList with data should match .JSON new file mode 100644 index 00000000..c855a660 --- /dev/null +++ b/server/subsonic/responses/.snapshots/Responses LyricsList with data should match .JSON @@ -0,0 +1,43 @@ +{ + "status": "ok", + "version": "1.8.0", + "type": "navidrome", + "serverVersion": "v0.0.0", + "openSubsonic": true, + "lyricsList": { + "structuredLyrics": [ + { + "displayArtist": "Rick Astley", + "displayTitle": "Never Gonna Give You Up", + "lang": "eng", + "line": [ + { + "start": 18800, + "value": "We're no strangers to love" + }, + { + "start": 22801, + "value": "You know the rules and so do I" + } + ], + "offset": 100, + "synced": true + }, + { + "displayArtist": "Rick Astley", + "displayTitle": "Never Gonna Give You Up", + "lang": "xxx", + "line": [ + { + "value": "We're no strangers to love" + }, + { + "value": "You know the rules and so do I" + } + ], + "offset": 100, + "synced": false + } + ] + } +} diff --git a/server/subsonic/responses/.snapshots/Responses LyricsList with data should match .XML b/server/subsonic/responses/.snapshots/Responses LyricsList with data should match .XML new file mode 100644 index 00000000..3f3e540d --- /dev/null +++ b/server/subsonic/responses/.snapshots/Responses LyricsList with data should match .XML @@ -0,0 +1,20 @@ + + + + + We're no strangers to love + + + You know the rules and so do I + + + + + We're no strangers to love + + + You know the rules and so do I + + + + diff --git a/server/subsonic/responses/.snapshots/Responses LyricsList without data should match .JSON b/server/subsonic/responses/.snapshots/Responses LyricsList without data should match .JSON new file mode 100644 index 00000000..876cc71c --- /dev/null +++ b/server/subsonic/responses/.snapshots/Responses LyricsList without data should match .JSON @@ -0,0 +1,8 @@ +{ + "status": "ok", + "version": "1.8.0", + "type": "navidrome", + "serverVersion": "v0.0.0", + "openSubsonic": true, + "lyricsList": {} +} diff --git a/server/subsonic/responses/.snapshots/Responses LyricsList without data should match .XML b/server/subsonic/responses/.snapshots/Responses LyricsList without data should match .XML new file mode 100644 index 00000000..040cf6b9 --- /dev/null +++ b/server/subsonic/responses/.snapshots/Responses LyricsList without data should match .XML @@ -0,0 +1,3 @@ + + + diff --git a/server/subsonic/responses/responses.go b/server/subsonic/responses/responses.go index eb5a8c88..f771176f 100644 --- a/server/subsonic/responses/responses.go +++ b/server/subsonic/responses/responses.go @@ -58,6 +58,7 @@ type Subsonic struct { JukeboxPlaylist *JukeboxPlaylist `xml:"jukeboxPlaylist,omitempty" json:"jukeboxPlaylist,omitempty"` OpenSubsonicExtensions *OpenSubsonicExtensions `xml:"openSubsonicExtensions,omitempty" json:"openSubsonicExtensions,omitempty"` + LyricsList *LyricsList `xml:"lyricsList,omitempty" json:"lyricsList,omitempty"` } type JsonWrapper struct { @@ -446,6 +447,26 @@ type JukeboxPlaylist struct { JukeboxStatus Entry []Child `xml:"entry,omitempty" json:"entry,omitempty"` } + +type Line struct { + Start *int64 `xml:"start,attr,omitempty" json:"start,omitempty"` + Value string `xml:"value" json:"value"` +} + +type StructuredLyric struct { + DisplayArtist string `xml:"displayArtist,attr,omitempty" json:"displayArtist,omitempty"` + DisplayTitle string `xml:"displayTitle,attr,omitempty" json:"displayTitle,omitempty"` + Lang string `xml:"lang,attr" json:"lang"` + Line []Line `xml:"line" json:"line"` + Offset *int64 `xml:"offset,attr,omitempty" json:"offset,omitempty"` + Synced bool `xml:"synced,attr" json:"synced"` +} + +type StructuredLyrics []StructuredLyric +type LyricsList struct { + StructuredLyrics []StructuredLyric `xml:"structuredLyrics,omitempty" json:"structuredLyrics,omitempty"` +} + type OpenSubsonicExtension struct { Name string `xml:"name,attr" json:"name"` Versions []int32 `xml:"versions" json:"versions"` diff --git a/server/subsonic/responses/responses_test.go b/server/subsonic/responses/responses_test.go index 47804d6b..b525ea7e 100644 --- a/server/subsonic/responses/responses_test.go +++ b/server/subsonic/responses/responses_test.go @@ -796,4 +796,69 @@ var _ = Describe("Responses", func() { }) }) }) + + Describe("LyricsList", func() { + BeforeEach(func() { + response.LyricsList = &LyricsList{} + }) + + Describe("without data", func() { + It("should match .XML", func() { + Expect(xml.MarshalIndent(response, "", " ")).To(MatchSnapshot()) + }) + It("should match .JSON", func() { + Expect(json.MarshalIndent(response, "", " ")).To(MatchSnapshot()) + }) + }) + + Describe("with data", func() { + BeforeEach(func() { + times := []int64{18800, 22801} + offset := int64(100) + + response.LyricsList.StructuredLyrics = StructuredLyrics{ + { + Lang: "eng", + DisplayArtist: "Rick Astley", + DisplayTitle: "Never Gonna Give You Up", + Offset: &offset, + Synced: true, + Line: []Line{ + { + Start: ×[0], + Value: "We're no strangers to love", + }, + { + Start: ×[1], + Value: "You know the rules and so do I", + }, + }, + }, + { + Lang: "xxx", + DisplayArtist: "Rick Astley", + DisplayTitle: "Never Gonna Give You Up", + Offset: &offset, + Synced: false, + Line: []Line{ + { + Value: "We're no strangers to love", + }, + { + Value: "You know the rules and so do I", + }, + }, + }, + } + }) + + It("should match .XML", func() { + Expect(xml.MarshalIndent(response, "", " ")).To(MatchSnapshot()) + }) + It("should match .JSON", func() { + Expect(json.MarshalIndent(response, "", " ")).To(MatchSnapshot()) + }) + }) + }) + }) diff --git a/tests/fixtures/01 Invisible (RED) Edit Version.m4a b/tests/fixtures/01 Invisible (RED) Edit Version.m4a index eb608b9b04ba934ff16a170a554b626ba0fb845f..c461fa0baf9856d1c58e67f76acffc5984664cf7 100644 GIT binary patch delta 193 zcmZqfWo+(c+^~?Dar5Ry%sPydRoE;i^RcsS4r0}1WOSX}z@{?!GmFCJdNwh}`l6LN zl|>8;4CX0`C5b?q5r`S04GgRd4D<{PVnZ@Aixo19xxgYudZq@krFq2#smYmXnW-s2 xmBz$sbInW7$t=zQYEema3~N=@A?$nleDa}c+oH2|)JGwlEX delta 103 zcmZqfWo+(c+^~?DF>~`GW*tUG*U1l9RVK%?DNJr*wcpIks>`^!o=uEVRbypNWf21d zgF;GTNg|MD1Y!oC%A(BVVnZ$ntza~ng~OkX*)y#uHFa|$$492g1sp+}gSZW?0Ro#H ANB{r; diff --git a/tests/fixtures/invalid-files/test-invalid-frame.mp3 b/tests/fixtures/invalid-files/test-invalid-frame.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..2f0fbc7b2b05cf7916c257841d2da8b56df05ed5 GIT binary patch literal 121385 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 U==jWe&e8tw_}?e+|H>2iKkN$MQ2+n{ literal 0 HcmV?d00001 diff --git a/tests/fixtures/test.aiff b/tests/fixtures/test.aiff index 148de3693841151d23718989ff7bae420350f222..220c4145c2f884e5307c65144dde6c6b9904f86a 100644 GIT binary patch delta 213 zcmeygi}lkk)`l&N<&o2;M=~C-4-NJSVPIegVqjoQ%}f9PkAdO;zi0*nAhZILdSEh^ zA%r1=A(Nq)K>GLwr9 gxgfN{H~Vw!WZE3WZD)Bi;x3Jo8=4CBn+Q{wux&GBu?lgQY02x z@CjCTEc*i}zrb&><}XstxNakL#o}6X&pqedhfhxmrzeHwPOEy2h?wll9Vzg-K~(5X zl8B`5apbp<*N>;gv(z# zZp{&#WV71=V)_J=<=`uTT^~Tfs7N;aT_l->s2Z|KOQF>5-hSn9 zp4qm7c8&^HxDFRi6?VR7mbwRhHlm%c0$byI{{sqP5-B-P{I_+Vu$0#^Lq9;lprN+8 zs8lP68s(}-=OV&H<1m@=QvSfk{eBjdVu;*cJ~r_3Prhi z(V(3EVb7M-``DInE1$Qs7|B>^TWMfKUJ{Lzj_sUO{GC6ZgS}WRuCV8co`wT;q{87w z@xGV&n~vR%`3caP=+v%T|Y9 H8vFkMbhVdm delta 493 zcmXw#&q~8U5XLu6)v8qR;!RrrU4*pRjTH)lq-iLjX}hFTh^I18E;0Q|XnWmJ~+8Xf;s~8=R>B z3`T=bkyF9~72d#lWoH^r3fjev5e;6WkvGIVB3>4y)xP{X6%LBY1C+hc4`34Vn$C#| zW>W+>L|us9C{I~IE8r_7mLp;5hd#j#11}uYy`*5BwhicA4Pf7X8wP#!XOn_~>$=!K z#t$5m+xlHix0+qNN2k%{rd}s%p-fq6GZueHxl97Ncp;r^;57b~&NE4zv-nTC*nl5f za*%)|#ozL5d7Hj@xWu#CxOsh6rYe*v8ii`hNkm#MUn`X2t(RXvvsC=~_`d8tRqp=* DES_s3 diff --git a/tests/fixtures/test.ogg b/tests/fixtures/test.ogg index 306d3bb133deb37b6c187ca7b9c42974dbf087d2..7c2d0efba3d15ac7875f3446472c6136ecac9b39 100644 GIT binary patch delta 315 zcmdmMI8S>*0EfiPy)1nH|JzTDR%Q`rU|^W+&uGKV4rDk5_}Us88BadWC@aVd*&0q>#VE_q4HR|p40aCj4{`LdHJU8Ls8Y`b6mtV>H8IpP2l3op{eoO= zgYuKJ^?*E|$RJPWVB2T|11kdqJp+T-kc`Y?h0J0uu!xbKsX=UMUU5Nca%Ng)YKj(d zI$ZP8b25uFg(TUgGLO|{d@eJ{CwGGKE$w}n_aY0T6xjER@h!4gO l337Dy4z@Kk0C^M4@eB2Jat*RIh6_NvXlTH=c|YqvApkd)P|pAW delta 309 zcmbQIz1MI;0C!4XteN0{5Qv)?q0B1Az`&p|`5>dnm^hX(tEFfcF#F)%Qu=B5At$H4IaUo?XO5L$srJun%| z5Wnq5&*fz@m~uts7G iF9jN2%uv9P%8(4SAPtC98B(?;nKr&M&wC delta 63 zcmeyioAujn)`l&Nxsmdr!9F1j3=Con49pP`5ez<+MVZOPhFlO@!DxC;B;$$gpQ0Gc Pn5IQD&YPYQ%h(41sp1ud diff --git a/tests/fixtures/test.wma b/tests/fixtures/test.wma index 5d08ab3c4b8bee9c17f6bf7aad10f97d0c2e677f..48241d21fff8135101f6768721e94aef06ecc8ff 100644 GIT binary patch delta 348 zcmdnKobl`mMj->WO{INl=LMJDWLU+JbS_7lbD~g-^d$xe5WIAG^{$oY1^b>c9NUJ@0gS(A7avp7s2W_1_lo# pyA6PLgWO7-`&@zUOJ~Rd`ygYoF0=OJW6T4*+1lPq+X8 delta 186 zcmX@Rf^qwDMj->WO{INl=LMJDWLU+JbS|frW1>)tbP@vu2wu9pde_SHf_={z4)7=h zr?N4aOx&m|Ed`ZkU~pJ+;S&4fOM=rsFf2G3w<3UTawcQ)WC13g$#F~ylYcNuOg_Vy zH(7?+WAYv*y~%YU;%_;U|@wZ9CMONa~Z(oIozyg(XPt7YzWeCbo&i00i_(TSIItMdE8yHv_80Z-o#D-*K7As^H zbAd&S^h^z6OZD=K3sRFa(=t<2@G5l8OV7zH&Y1i@LUpobq#7gp#s91FbFb$!2$@yz`zV;I0g7J7#bPFxGtH+$qa`2Ml4Y2$kfCl z1|tJQ6PSQ|YF<$)Lr{KlHcTX>C^0#k!C2qWfEB9AF(;`smjO(Ige*bAA(pF diff --git a/tests/mock_ffmpeg.go b/tests/mock_ffmpeg.go index aebef53d..31ac7d9a 100644 --- a/tests/mock_ffmpeg.go +++ b/tests/mock_ffmpeg.go @@ -19,6 +19,10 @@ type MockFFmpeg struct { Error error } +func (ff *MockFFmpeg) IsAvailable() bool { + return true +} + func (ff *MockFFmpeg) Transcode(context.Context, string, string, int, int) (io.ReadCloser, error) { if ff.Error != nil { return nil, ff.Error diff --git a/ui/src/reducers/playerReducer.js b/ui/src/reducers/playerReducer.js index a6597ef3..3600ec82 100644 --- a/ui/src/reducers/playerReducer.js +++ b/ui/src/reducers/playerReducer.js @@ -20,8 +20,14 @@ const initialState = { savedPlayIndex: 0, } -const timestampRegex = - /(\[([0-9]{1,2}:)?([0-9]{1,2}:)([0-9]{1,2})(\.[0-9]{1,2})?\])/g +const pad = (value) => { + const str = value.toString() + if (str.length === 1) { + return `0${str}` + } else { + return str + } +} const mapToAudioLists = (item) => { // If item comes from a playlist, trackId is mediaFileId @@ -40,12 +46,33 @@ const mapToAudioLists = (item) => { } const { lyrics } = item + let lyricText = '' + + if (lyrics) { + const structured = JSON.parse(lyrics) + for (const structuredLyric of structured) { + if (structuredLyric.synced) { + for (const line of structuredLyric.line) { + let time = Math.floor(line.start / 10) + const ms = time % 100 + time = Math.floor(time / 100) + const sec = time % 60 + time = Math.floor(time / 60) + const min = time % 60 + + ms.toString() + lyricText += `[${pad(min)}:${pad(sec)}.${pad(ms)}] ${line.value}\n` + } + } + } + } + return { trackId, uuid: uuidv4(), song: item, name: item.title, - lyric: timestampRegex.test(lyrics) ? lyrics : '', + lyric: lyricText, singer: item.artist, duration: item.duration, musicSrc: subsonic.streamUrl(trackId),