package library
import (
"context"
"encoding/base64"
"encoding/binary"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestFilterComments(t *testing.T) {
got := filterComments([]string{
"TRACKNUMBER=1",
"GENRE=IDM",
"TITLE=Song",
"REPLAYGAIN_TRACK_GAIN=-3 dB",
"",
"no-equals-sign",
"ALBUMARTIST=Band",
"DATE=2001",
})
want := []string{"TRACKNUMBER=1", "TITLE=Song", "ALBUMARTIST=Band", "DATE=2001"}
if len(got) != len(want) {
t.Fatalf("got %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Errorf("comment %d = %q, want %q", i, got[i], want[i])
}
}
}
func TestPictureCommentStructure(t *testing.T) {
dir := t.TempDir()
img := filepath.Join(dir, "cover.jpg")
payload := []byte{0xFF, 0xD8, 0xFF, 0xE0, 1, 2, 3, 4}
if err := os.WriteFile(img, payload, 0o644); err != nil {
t.Fatal(err)
}
comment, err := pictureComment(context.Background(), img)
if err != nil {
t.Fatal(err)
}
const prefix = "METADATA_BLOCK_PICTURE="
if !strings.HasPrefix(comment, prefix) {
t.Fatalf("comment should start with %s", prefix)
}
raw, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(comment, prefix))
if err != nil {
t.Fatalf("payload is not valid base64: %v", err)
}
if got := binary.BigEndian.Uint32(raw[0:4]); got != 3 {
t.Errorf("picture type = %d, want 3 (front cover)", got)
}
mimeLen := int(binary.BigEndian.Uint32(raw[4:8]))
mime := string(raw[8 : 8+mimeLen])
if mime != "image/jpeg" {
t.Errorf("mime = %q, want image/jpeg", mime)
}
off := 8 + mimeLen
descLen := int(binary.BigEndian.Uint32(raw[off : off+4]))
off += 4 + descLen + 16
dataLen := int(binary.BigEndian.Uint32(raw[off : off+4]))
off += 4
if dataLen != len(payload) {
t.Fatalf("data length = %d, want %d", dataLen, len(payload))
}
if string(raw[off:off+dataLen]) != string(payload) {
t.Error("image bytes were not preserved")
}
}
// A cover that cannot be read used to be skipped in silence. The usual reason is
// a cover that lived in the staging folder and has just been cleared away by the
// filing run, so the album gets a library copy with no artwork at all and every
// summary still names the cover it thought it used.
func TestAMissingCoverIsReported(t *testing.T) {
needTools(t, "ffmpeg", "metaflac", "ffprobe")
dir := t.TempDir()
master := filepath.Join(dir, "master.flac")
if out, err := exec.Command("ffmpeg", "-hide_banner", "-loglevel", "error", "-y",
"-f", "lavfi", "-i", "sine=frequency=440:duration=0.1", master).CombinedOutput(); err != nil {
t.Fatalf("building a master: %v %s", err, out)
}
if out, err := exec.Command("metaflac", "--set-tag=TITLE=One",
"--set-tag=ALBUM=An Album",
"--set-tag=ALBUMARTIST=Band",
"--set-tag=TRACKNUMBER=1", master).CombinedOutput(); err != nil {
t.Fatalf("tagging the master: %v %s", err, out)
}
out := filepath.Join(dir, "library", "album")
res, err := Transcode(context.Background(), []string{master}, []string{"1-01_one.flac"}, out, Options{
CoverPath: filepath.Join(dir, "staging", "cover.jpg"),
})
if err != nil {
t.Fatal(err)
}
found := false
for _, w := range res.Warnings {
if strings.Contains(w, "cover") {
found = true
}
}
if !found {
t.Errorf("a cover that could not be read was not reported: %v", res.Warnings)
}
}
// Rebuilding a library used to remove the old directory first and rename the new
// one in afterwards, so a failure in between destroyed the old copy and threw
// away the new one. Nothing should be left behind when it succeeds, either.
func TestRebuildingALibraryLeavesNoLitter(t *testing.T) {
needTools(t, "ffmpeg", "metaflac", "ffprobe")
dir := t.TempDir()
master := filepath.Join(dir, "master.flac")
if out, err := exec.Command("ffmpeg", "-hide_banner", "-loglevel", "error", "-y",
"-f", "lavfi", "-i", "sine=frequency=440:duration=0.1", master).CombinedOutput(); err != nil {
t.Fatalf("building a master: %v %s", err, out)
}
if out, err := exec.Command("metaflac", "--set-tag=TITLE=One",
"--set-tag=ALBUM=An Album",
"--set-tag=ALBUMARTIST=Band",
"--set-tag=TRACKNUMBER=1", master).CombinedOutput(); err != nil {
t.Fatalf("tagging the master: %v %s", err, out)
}
out := filepath.Join(dir, "library", "album")
for i := 0; i < 2; i++ {
if _, err := Transcode(context.Background(), []string{master}, []string{"1-01_one.flac"}, out, Options{}); err != nil {
t.Fatal(err)
}
}
entries, err := os.ReadDir(filepath.Dir(out))
if err != nil {
t.Fatal(err)
}
for _, e := range entries {
if e.Name() != "album" {
t.Errorf("rebuilding left %s behind", e.Name())
}
}
tracks, err := os.ReadDir(out)
if err != nil {
t.Fatal(err)
}
if len(tracks) != 1 || !strings.HasSuffix(tracks[0].Name(), ".ogg") {
var names []string
for _, e := range tracks {
names = append(names, e.Name())
}
t.Errorf("library contents after a rebuild: %v", names)
}
}
func needTools(t *testing.T, tools ...string) {
t.Helper()
for _, tool := range tools {
if _, err := exec.LookPath(tool); err != nil {
t.Skipf("%s is needed to build a fixture", tool)
}
}
}