mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-12 02:37:18 +02:00
feat: add audio quality analysis widget and fix USLT lyrics detection
This commit is contained in:
1 parent
5340ca7b16
commit
091e3fadd9
22 files changed
+1822
-26
No files matched your search
@@ -498,7 +498,13 @@ func extractUserTextFrame(data []byte) (string, string) {
|
||||
|
||||
func isLyricsDescription(description string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(description)) {
|
||||
case "lyrics", "lyric", "unsyncedlyrics", "unsynced lyrics", "lrc":
|
||||
case
|
||||
"lyrics",
|
||||
"lyric",
|
||||
"unsyncedlyrics",
|
||||
"unsynced lyrics",
|
||||
"uslt",
|
||||
"lrc":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package gobackend
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func ffmpegCommand(args ...string) *exec.Cmd {
|
||||
if ffmpegPath, err := exec.LookPath("ffmpeg"); err == nil {
|
||||
return exec.Command(ffmpegPath, args...)
|
||||
}
|
||||
return exec.Command("ffmpeg", args...)
|
||||
}
|
||||
|
||||
func runFFmpegTestCommand(t *testing.T, args ...string) {
|
||||
t.Helper()
|
||||
cmd := ffmpegCommand(args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("ffmpeg failed: %v\n%s", err, string(output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractLyricsReadsMp3AfterCoverEmbed(t *testing.T) {
|
||||
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
||||
t.Skip("ffmpeg not available")
|
||||
}
|
||||
|
||||
tempDir := t.TempDir()
|
||||
sourceFlac := filepath.Join(tempDir, "source.flac")
|
||||
baseMp3 := filepath.Join(tempDir, "base.mp3")
|
||||
finalMp3 := filepath.Join(tempDir, "final.mp3")
|
||||
coverPath := filepath.Join(tempDir, "cover.jpg")
|
||||
lyrics := "[ti:Test Song]\n[ar:Test Artist]\n[00:00.00]Hello from embedded lyrics"
|
||||
|
||||
runFFmpegTestCommand(
|
||||
t,
|
||||
"-y",
|
||||
"-f",
|
||||
"lavfi",
|
||||
"-i",
|
||||
"sine=frequency=440:duration=1",
|
||||
"-c:a",
|
||||
"flac",
|
||||
sourceFlac,
|
||||
)
|
||||
|
||||
runFFmpegTestCommand(
|
||||
t,
|
||||
"-y",
|
||||
"-f",
|
||||
"lavfi",
|
||||
"-i",
|
||||
"color=c=red:s=32x32:d=1",
|
||||
"-frames:v",
|
||||
"1",
|
||||
coverPath,
|
||||
)
|
||||
|
||||
runFFmpegTestCommand(
|
||||
t,
|
||||
"-y",
|
||||
"-i",
|
||||
sourceFlac,
|
||||
"-b:a",
|
||||
"320k",
|
||||
"-metadata",
|
||||
"title=Test Song",
|
||||
"-metadata",
|
||||
"artist=Test Artist",
|
||||
"-metadata",
|
||||
"lyrics="+lyrics,
|
||||
baseMp3,
|
||||
)
|
||||
|
||||
runFFmpegTestCommand(
|
||||
t,
|
||||
"-y",
|
||||
"-i",
|
||||
baseMp3,
|
||||
"-i",
|
||||
coverPath,
|
||||
"-map",
|
||||
"0:a",
|
||||
"-map_metadata",
|
||||
"-1",
|
||||
"-map",
|
||||
"1:0",
|
||||
"-c:v:0",
|
||||
"copy",
|
||||
"-id3v2_version",
|
||||
"3",
|
||||
"-metadata",
|
||||
"title=Test Song",
|
||||
"-metadata",
|
||||
"artist=Test Artist",
|
||||
"-metadata",
|
||||
"lyrics="+lyrics,
|
||||
"-metadata:s:v",
|
||||
"title=Album cover",
|
||||
"-metadata:s:v",
|
||||
"comment=Cover (front)",
|
||||
"-c:a",
|
||||
"copy",
|
||||
finalMp3,
|
||||
)
|
||||
|
||||
meta, err := ReadID3Tags(finalMp3)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadID3Tags failed: %v", err)
|
||||
}
|
||||
if meta == nil {
|
||||
t.Fatalf("ReadID3Tags returned nil metadata")
|
||||
}
|
||||
|
||||
embeddedLyrics, err := ExtractLyrics(finalMp3)
|
||||
if err != nil {
|
||||
t.Fatalf("ExtractLyrics failed: %v (metadata=%+v)", err, meta)
|
||||
}
|
||||
if !strings.Contains(embeddedLyrics, "Hello from embedded lyrics") {
|
||||
t.Fatalf("embedded lyrics missing, got %q (metadata=%+v)", embeddedLyrics, meta)
|
||||
}
|
||||
if !strings.Contains(meta.Lyrics, "Hello from embedded lyrics") {
|
||||
t.Fatalf("ReadID3Tags lyrics missing, got %+v", meta)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(finalMp3); err != nil {
|
||||
t.Fatalf("expected final mp3 to exist: %v", err)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -12,6 +12,7 @@ require (
|
||||
github.com/refraction-networking/utls v1.8.2
|
||||
golang.org/x/mobile v0.0.0-20260211191516-dcd2a3258864
|
||||
golang.org/x/net v0.50.0
|
||||
golang.org/x/text v0.34.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -24,6 +25,5 @@ require (
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
)
|
||||
@@ -6,8 +6,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3 h1:bVp3yUzvSAJzu9GqID+Z96P+eu5TKnIMJSV4QaZMauM=
|
||||
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
|
||||
github.com/dop251/goja v0.0.0-20260216154549-8b74ce4618c5 h1:QckvTXtu55YMopmVeDrPQ/r+T6xjw8KMCmE3UgUldkw=
|
||||
github.com/dop251/goja v0.0.0-20260216154549-8b74ce4618c5/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
|
||||
github.com/go-flac/flacpicture/v2 v2.0.2 h1:HCaJIVZpxnpdWs6G3ECEVRelzqS5xOi1Ba1AGmtXbzE=
|
||||
@@ -30,36 +28,20 @@ github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEv
|
||||
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/mobile v0.0.0-20260204172633-1dceadbbeea3 h1:NiJtT7g4ncNFVjVZMAYNBrPSNhIjFYPj8UKA8MEw2A4=
|
||||
golang.org/x/mobile v0.0.0-20260204172633-1dceadbbeea3/go.mod h1:wReH3Q1agKmmLapipWFnd4NSs8KPz3fK6mSEZjXLkrg=
|
||||
golang.org/x/mobile v0.0.0-20260209203831-923679eb55af h1:VqXrZNyqFISxo0rNDFZQlRDRIp7RXSJDeh/LbrK+W1k=
|
||||
golang.org/x/mobile v0.0.0-20260209203831-923679eb55af/go.mod h1:tbwefIr7RlQD1OpZ0KEZ9nux/uiihAOGdafgZfJkmII=
|
||||
golang.org/x/mobile v0.0.0-20260211191516-dcd2a3258864 h1:cTVynMSsMYgbUrtia2HB1jrhdUwQNtQti91vUCyjMp4=
|
||||
golang.org/x/mobile v0.0.0-20260211191516-dcd2a3258864/go.mod h1:4OGHIUSBiIqyFAQDaX1tpY0BVnO20DvNDeATBu8aeFQ=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
|
||||
Reference in new issue
Block a user