sylt, move some things to wrapper

This commit is contained in:
Kendall Garner 2023-12-03 15:03:46 -08:00
commit c1d4fa8780
No known key found for this signature in database
GPG key ID: 18D2767419676C87
136 changed files with 985 additions and 566 deletions

View file

@ -47,7 +47,7 @@ lintall: lint ##@Development Lint Go and JS code
format: ##@Development Format code
@(cd ./ui && npm run prettier)
@go run golang.org/x/tools/cmd/goimports -w `find . -name '*.go' | grep -v _gen.go$$`
@go run golang.org/x/tools/cmd/goimports@latest -w `find . -name '*.go' | grep -v _gen.go$$`
@go mod tidy
.PHONY: format

View file

@ -96,6 +96,7 @@ type configOptions struct {
DevSidebarPlaylists bool
DevEnableBufferedScrobble bool
DevShowArtistPage bool
DevOffsetOptimize int
DevArtworkMaxRequests int
DevArtworkThrottleBacklogLimit int
DevArtworkThrottleBacklogTimeout time.Duration
@ -352,6 +353,7 @@ func init() {
viper.SetDefault("devenablebufferedscrobble", true)
viper.SetDefault("devsidebarplaylists", true)
viper.SetDefault("devshowartistpage", true)
viper.SetDefault("devoffsetoptimize", 50000)
viper.SetDefault("devartworkmaxrequests", number.Max(2, runtime.NumCPU()/3))
viper.SetDefault("devartworkthrottlebackloglimit", consts.RequestThrottleBacklogLimit)
viper.SetDefault("devartworkthrottlebacklogtimeout", consts.RequestThrottleBacklogTimeout)

View file

@ -94,19 +94,19 @@ var (
"name": "mp3 audio",
"targetFormat": "mp3",
"defaultBitRate": 192,
"command": "ffmpeg -i %s -map 0:a:0 -b:a %bk -v 0 -f mp3 -",
"command": "ffmpeg -i %s -ss %t -map 0:a:0 -b:a %bk -v 0 -f mp3 -",
},
{
"name": "opus audio",
"targetFormat": "opus",
"defaultBitRate": 128,
"command": "ffmpeg -i %s -map 0:a:0 -b:a %bk -v 0 -c:a libopus -f opus -",
"command": "ffmpeg -i %s -ss %t -map 0:a:0 -b:a %bk -v 0 -c:a libopus -f opus -",
},
{
"name": "aac audio",
"targetFormat": "aac",
"defaultBitRate": 256,
"command": "ffmpeg -i %s -map 0:a:0 -b:a %bk -v 0 -c:a aac -f adts -",
"command": "ffmpeg -i %s -ss %t -map 0:a:0 -b:a %bk -v 0 -c:a aac -f adts -",
},
}

View file

@ -150,7 +150,7 @@ func (a *archiver) addFileToZip(ctx context.Context, z *zip.Writer, mf model.Med
var r io.ReadCloser
if format != "raw" && format != "" {
r, err = a.ms.DoStream(ctx, &mf, format, bitrate)
r, err = a.ms.DoStream(ctx, &mf, format, bitrate, 0)
} else {
r, err = os.Open(mf.Path)
}

View file

@ -44,7 +44,7 @@ var _ = Describe("Archiver", func() {
}}).Return(mfs, nil)
ds.On("MediaFile", mock.Anything).Return(mfRepo)
ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128).Return(io.NopCloser(strings.NewReader("test")), nil).Times(3)
ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128, 0).Return(io.NopCloser(strings.NewReader("test")), nil).Times(3)
out := new(bytes.Buffer)
err := arch.ZipAlbum(context.Background(), "1", "mp3", 128, out)
@ -73,7 +73,7 @@ var _ = Describe("Archiver", func() {
}}).Return(mfs, nil)
ds.On("MediaFile", mock.Anything).Return(mfRepo)
ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2)
ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128, 0).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2)
out := new(bytes.Buffer)
err := arch.ZipArtist(context.Background(), "1", "mp3", 128, out)
@ -104,7 +104,7 @@ var _ = Describe("Archiver", func() {
}
sh.On("Load", mock.Anything, "1").Return(share, nil)
ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2)
ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128, 0).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2)
out := new(bytes.Buffer)
err := arch.ZipShare(context.Background(), "1", out)
@ -136,7 +136,7 @@ var _ = Describe("Archiver", func() {
plRepo := &mockPlaylistRepository{}
plRepo.On("GetWithTracks", "1", true).Return(pls, nil)
ds.On("Playlist", mock.Anything).Return(plRepo)
ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2)
ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128, 0).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2)
out := new(bytes.Buffer)
err := arch.ZipPlaylist(context.Background(), "1", "mp3", 128, out)
@ -192,8 +192,8 @@ type mockMediaStreamer struct {
core.MediaStreamer
}
func (m *mockMediaStreamer) DoStream(ctx context.Context, mf *model.MediaFile, format string, bitrate int) (*core.Stream, error) {
args := m.Called(ctx, mf, format, bitrate)
func (m *mockMediaStreamer) DoStream(ctx context.Context, mf *model.MediaFile, reqFormat string, reqBitRate int, reqOffset int) (*core.Stream, error) {
args := m.Called(ctx, mf, reqFormat, reqBitRate, reqOffset)
if args.Error(1) != nil {
return nil, args.Error(1)
}

View file

@ -16,7 +16,7 @@ import (
)
type FFmpeg interface {
Transcode(ctx context.Context, command, path string, maxBitRate int) (io.ReadCloser, error)
Transcode(ctx context.Context, command, path string, maxBitRate, offset int) (io.ReadCloser, error)
ExtractImage(ctx context.Context, path string) (io.ReadCloser, error)
ConvertToWAV(ctx context.Context, path string) (io.ReadCloser, error)
ConvertToFLAC(ctx context.Context, path string) (io.ReadCloser, error)
@ -37,11 +37,11 @@ const (
type ffmpeg struct{}
func (e *ffmpeg) Transcode(ctx context.Context, command, path string, maxBitRate int) (io.ReadCloser, error) {
func (e *ffmpeg) Transcode(ctx context.Context, command, path string, maxBitRate, offset int) (io.ReadCloser, error) {
if _, err := ffmpegCmd(); err != nil {
return nil, err
}
args := createFFmpegCommand(command, path, maxBitRate)
args := createFFmpegCommand(command, path, maxBitRate, offset)
return e.start(ctx, args)
}
@ -49,17 +49,17 @@ func (e *ffmpeg) ExtractImage(ctx context.Context, path string) (io.ReadCloser,
if _, err := ffmpegCmd(); err != nil {
return nil, err
}
args := createFFmpegCommand(extractImageCmd, path, 0)
args := createFFmpegCommand(extractImageCmd, path, 0, 0)
return e.start(ctx, args)
}
func (e *ffmpeg) ConvertToWAV(ctx context.Context, path string) (io.ReadCloser, error) {
args := createFFmpegCommand(createWavCmd, path, 0)
args := createFFmpegCommand(createWavCmd, path, 0, 0)
return e.start(ctx, args)
}
func (e *ffmpeg) ConvertToFLAC(ctx context.Context, path string) (io.ReadCloser, error) {
args := createFFmpegCommand(createFLACCmd, path, 0)
args := createFFmpegCommand(createFLACCmd, path, 0, 0)
return e.start(ctx, args)
}
@ -127,15 +127,25 @@ func (j *ffCmd) wait() {
}
// Path will always be an absolute path
func createFFmpegCommand(cmd, path string, maxBitRate int) []string {
func createFFmpegCommand(cmd, path string, maxBitRate, offset int) []string {
split := strings.Split(fixCmd(cmd), " ")
for i, s := range split {
s = strings.ReplaceAll(s, "%s", path)
s = strings.ReplaceAll(s, "%b", strconv.Itoa(maxBitRate))
split[i] = s
var parts []string
for _, s := range split {
if strings.Contains(s, "%s") {
s = strings.ReplaceAll(s, "%s", path)
parts = append(parts, s)
if offset > 0 && !strings.Contains(cmd, "%t") {
parts = append(parts, "-ss", strconv.Itoa(offset))
}
} else {
s = strings.ReplaceAll(s, "%t", strconv.Itoa(offset))
s = strings.ReplaceAll(s, "%b", strconv.Itoa(maxBitRate))
parts = append(parts, s)
}
}
return split
return parts
}
func createProbeCommand(cmd string, inputs []string) []string {

View file

@ -24,9 +24,22 @@ var _ = Describe("ffmpeg", func() {
})
Describe("createFFmpegCommand", func() {
It("creates a valid command line", func() {
args := createFFmpegCommand("ffmpeg -i %s -b:a %bk mp3 -", "/music library/file.mp3", 123)
args := createFFmpegCommand("ffmpeg -i %s -b:a %bk mp3 -", "/music library/file.mp3", 123, 0)
Expect(args).To(Equal([]string{"ffmpeg", "-i", "/music library/file.mp3", "-b:a", "123k", "mp3", "-"}))
})
Context("when command has time offset param", func() {
It("creates a valid command line with offset", func() {
args := createFFmpegCommand("ffmpeg -i %s -b:a %bk -ss %t mp3 -", "/music library/file.mp3", 123, 456)
Expect(args).To(Equal([]string{"ffmpeg", "-i", "/music library/file.mp3", "-b:a", "123k", "-ss", "456", "mp3", "-"}))
})
})
Context("when command does not have time offset param", func() {
It("adds time offset after the input file name", func() {
args := createFFmpegCommand("ffmpeg -i %s -b:a %bk mp3 -", "/music library/file.mp3", 123, 456)
Expect(args).To(Equal([]string{"ffmpeg", "-i", "/music library/file.mp3", "-ss", "456", "-b:a", "123k", "mp3", "-"}))
})
})
})
Describe("createProbeCommand", func() {

View file

@ -19,8 +19,8 @@ import (
)
type MediaStreamer interface {
NewStream(ctx context.Context, id string, reqFormat string, reqBitRate int) (*Stream, error)
DoStream(ctx context.Context, mf *model.MediaFile, reqFormat string, reqBitRate int) (*Stream, error)
NewStream(ctx context.Context, id string, reqFormat string, reqBitRate int, offset int) (*Stream, error)
DoStream(ctx context.Context, mf *model.MediaFile, reqFormat string, reqBitRate int, reqOffset int) (*Stream, error)
}
type TranscodingCache cache.FileCache
@ -40,22 +40,23 @@ type streamJob struct {
mf *model.MediaFile
format string
bitRate int
offset int
}
func (j *streamJob) Key() string {
return fmt.Sprintf("%s.%s.%d.%s", j.mf.ID, j.mf.UpdatedAt.Format(time.RFC3339Nano), j.bitRate, j.format)
return fmt.Sprintf("%s.%s.%d.%s.%d", j.mf.ID, j.mf.UpdatedAt.Format(time.RFC3339Nano), j.bitRate, j.format, j.offset)
}
func (ms *mediaStreamer) NewStream(ctx context.Context, id string, reqFormat string, reqBitRate int) (*Stream, error) {
func (ms *mediaStreamer) NewStream(ctx context.Context, id string, reqFormat string, reqBitRate int, reqOffset int) (*Stream, error) {
mf, err := ms.ds.MediaFile(ctx).Get(id)
if err != nil {
return nil, err
}
return ms.DoStream(ctx, mf, reqFormat, reqBitRate)
return ms.DoStream(ctx, mf, reqFormat, reqBitRate, reqOffset)
}
func (ms *mediaStreamer) DoStream(ctx context.Context, mf *model.MediaFile, reqFormat string, reqBitRate int) (*Stream, error) {
func (ms *mediaStreamer) DoStream(ctx context.Context, mf *model.MediaFile, reqFormat string, reqBitRate int, reqOffset int) (*Stream, error) {
var format string
var bitRate int
var cached bool
@ -70,7 +71,7 @@ func (ms *mediaStreamer) DoStream(ctx context.Context, mf *model.MediaFile, reqF
if format == "raw" {
log.Debug(ctx, "Streaming RAW file", "id", mf.ID, "path", mf.Path,
"requestBitrate", reqBitRate, "requestFormat", reqFormat,
"requestBitrate", reqBitRate, "requestFormat", reqFormat, "requestOffset", reqOffset,
"originalBitrate", mf.BitRate, "originalFormat", mf.Suffix,
"selectedBitrate", bitRate, "selectedFormat", format)
f, err := os.Open(mf.Path)
@ -88,6 +89,7 @@ func (ms *mediaStreamer) DoStream(ctx context.Context, mf *model.MediaFile, reqF
mf: mf,
format: format,
bitRate: bitRate,
offset: reqOffset,
}
r, err := ms.cache.Get(ctx, job)
if err != nil {
@ -100,7 +102,7 @@ func (ms *mediaStreamer) DoStream(ctx context.Context, mf *model.MediaFile, reqF
s.Seeker = r.Seeker
log.Debug(ctx, "Streaming TRANSCODED file", "id", mf.ID, "path", mf.Path,
"requestBitrate", reqBitRate, "requestFormat", reqFormat,
"requestBitrate", reqBitRate, "requestFormat", reqFormat, "requestOffset", reqOffset,
"originalBitrate", mf.BitRate, "originalFormat", mf.Suffix,
"selectedBitrate", bitRate, "selectedFormat", format, "cached", cached, "seekable", s.Seekable())
@ -199,7 +201,7 @@ func NewTranscodingCache() TranscodingCache {
log.Error(ctx, "Error loading transcoding command", "format", job.format, err)
return nil, os.ErrInvalid
}
out, err := job.ms.transcoder.Transcode(ctx, t.Command, job.mf.Path, job.bitRate)
out, err := job.ms.transcoder.Transcode(ctx, t.Command, job.mf.Path, job.bitRate, job.offset)
if err != nil {
log.Error(ctx, "Error starting transcoder", "id", job.mf.ID, err)
return nil, os.ErrInvalid

View file

@ -39,34 +39,34 @@ var _ = Describe("MediaStreamer", func() {
Context("NewStream", func() {
It("returns a seekable stream if format is 'raw'", func() {
s, err := streamer.NewStream(ctx, "123", "raw", 0)
s, err := streamer.NewStream(ctx, "123", "raw", 0, 0)
Expect(err).ToNot(HaveOccurred())
Expect(s.Seekable()).To(BeTrue())
})
It("returns a seekable stream if maxBitRate is 0", func() {
s, err := streamer.NewStream(ctx, "123", "mp3", 0)
s, err := streamer.NewStream(ctx, "123", "mp3", 0, 0)
Expect(err).ToNot(HaveOccurred())
Expect(s.Seekable()).To(BeTrue())
})
It("returns a seekable stream if maxBitRate is higher than file bitRate", func() {
s, err := streamer.NewStream(ctx, "123", "mp3", 320)
s, err := streamer.NewStream(ctx, "123", "mp3", 320, 0)
Expect(err).ToNot(HaveOccurred())
Expect(s.Seekable()).To(BeTrue())
})
It("returns a NON seekable stream if transcode is required", func() {
s, err := streamer.NewStream(ctx, "123", "mp3", 64)
s, err := streamer.NewStream(ctx, "123", "mp3", 64, 0)
Expect(err).To(BeNil())
Expect(s.Seekable()).To(BeFalse())
Expect(s.Duration()).To(Equal(float32(257.0)))
})
It("returns a seekable stream if the file is complete in the cache", func() {
s, err := streamer.NewStream(ctx, "123", "mp3", 32)
s, err := streamer.NewStream(ctx, "123", "mp3", 32, 0)
Expect(err).To(BeNil())
_, _ = io.ReadAll(s)
_ = s.Close()
Eventually(func() bool { return ffmpeg.IsClosed() }, "3s").Should(BeTrue())
s, err = streamer.NewStream(ctx, "123", "mp3", 32)
s, err = streamer.NewStream(ctx, "123", "mp3", 32, 0)
Expect(err).To(BeNil())
Expect(s.Seekable()).To(BeTrue())
})

View file

@ -1,6 +1,7 @@
package migrations
import (
"context"
"database/sql"
"github.com/navidrome/navidrome/log"
@ -8,10 +9,10 @@ import (
)
func init() {
goose.AddMigration(Up20200130083147, Down20200130083147)
goose.AddMigrationContext(Up20200130083147, Down20200130083147)
}
func Up20200130083147(tx *sql.Tx) error {
func Up20200130083147(_ context.Context, tx *sql.Tx) error {
log.Info("Creating DB Schema")
_, err := tx.Exec(`
create table if not exists album
@ -178,6 +179,6 @@ create table if not exists user
return err
}
func Down20200130083147(tx *sql.Tx) error {
func Down20200130083147(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200131183653, Down20200131183653)
goose.AddMigrationContext(Up20200131183653, Down20200131183653)
}
func Up20200131183653(tx *sql.Tx) error {
func Up20200131183653(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table search_dg_tmp
(
@ -36,7 +37,7 @@ update annotation set item_type = 'media_file' where item_type = 'mediaFile';
return err
}
func Down20200131183653(tx *sql.Tx) error {
func Down20200131183653(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table search_dg_tmp
(

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200208222418, Down20200208222418)
goose.AddMigrationContext(Up20200208222418, Down20200208222418)
}
func Up20200208222418(tx *sql.Tx) error {
func Up20200208222418(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
update annotation set play_count = 0 where play_count is null;
update annotation set rating = 0 where rating is null;
@ -50,6 +51,6 @@ create index annotation_starred
return err
}
func Down20200208222418(tx *sql.Tx) error {
func Down20200208222418(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200220143731, Down20200220143731)
goose.AddMigrationContext(Up20200220143731, Down20200220143731)
}
func Up20200220143731(tx *sql.Tx) error {
func Up20200220143731(_ context.Context, tx *sql.Tx) error {
notice(tx, "This migration will force the next scan to be a full rescan!")
_, err := tx.Exec(`
create table media_file_dg_tmp
@ -124,6 +125,6 @@ update media_file set updated_at = '0001-01-01';
return err
}
func Down20200220143731(tx *sql.Tx) error {
func Down20200220143731(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,20 +1,21 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200310171621, Down20200310171621)
goose.AddMigrationContext(Up20200310171621, Down20200310171621)
}
func Up20200310171621(tx *sql.Tx) error {
func Up20200310171621(_ context.Context, tx *sql.Tx) error {
notice(tx, "A full rescan will be performed to enable search by Album Artist!")
return forceFullRescan(tx)
}
func Down20200310171621(tx *sql.Tx) error {
func Down20200310171621(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200310181627, Down20200310181627)
goose.AddMigrationContext(Up20200310181627, Down20200310181627)
}
func Up20200310181627(tx *sql.Tx) error {
func Up20200310181627(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table transcoding
(
@ -44,7 +45,7 @@ create table player
return err
}
func Down20200310181627(tx *sql.Tx) error {
func Down20200310181627(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
drop table transcoding;
drop table player;

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200319211049, Down20200319211049)
goose.AddMigrationContext(Up20200319211049, Down20200319211049)
}
func Up20200319211049(tx *sql.Tx) error {
func Up20200319211049(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table media_file
add full_text varchar(255) default '';
@ -36,6 +37,6 @@ drop table if exists search;
return forceFullRescan(tx)
}
func Down20200319211049(tx *sql.Tx) error {
func Down20200319211049(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200325185135, Down20200325185135)
goose.AddMigrationContext(Up20200325185135, Down20200325185135)
}
func Up20200325185135(tx *sql.Tx) error {
func Up20200325185135(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table album
add album_artist_id varchar(255) default '';
@ -29,6 +30,6 @@ create index media_file_artist_album_id
return forceFullRescan(tx)
}
func Down20200325185135(tx *sql.Tx) error {
func Down20200325185135(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,20 +1,21 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200326090707, Down20200326090707)
goose.AddMigrationContext(Up20200326090707, Down20200326090707)
}
func Up20200326090707(tx *sql.Tx) error {
func Up20200326090707(_ context.Context, tx *sql.Tx) error {
notice(tx, "A full rescan will be performed!")
return forceFullRescan(tx)
}
func Down20200326090707(tx *sql.Tx) error {
func Down20200326090707(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200327193744, Down20200327193744)
goose.AddMigrationContext(Up20200327193744, Down20200327193744)
}
func Up20200327193744(tx *sql.Tx) error {
func Up20200327193744(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table album_dg_tmp
(
@ -75,6 +76,6 @@ create index album_max_year
return forceFullRescan(tx)
}
func Down20200327193744(tx *sql.Tx) error {
func Down20200327193744(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200404214704, Down20200404214704)
goose.AddMigrationContext(Up20200404214704, Down20200404214704)
}
func Up20200404214704(tx *sql.Tx) error {
func Up20200404214704(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create index if not exists media_file_year
on media_file (year);
@ -24,6 +25,6 @@ create index if not exists media_file_track_number
return err
}
func Down20200404214704(tx *sql.Tx) error {
func Down20200404214704(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,20 +1,21 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200409002249, Down20200409002249)
goose.AddMigrationContext(Up20200409002249, Down20200409002249)
}
func Up20200409002249(tx *sql.Tx) error {
func Up20200409002249(_ context.Context, tx *sql.Tx) error {
notice(tx, "A full rescan will be performed to enable search by individual Artist in an Album!")
return forceFullRescan(tx)
}
func Down20200409002249(tx *sql.Tx) error {
func Down20200409002249(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200411164603, Down20200411164603)
goose.AddMigrationContext(Up20200411164603, Down20200411164603)
}
func Up20200411164603(tx *sql.Tx) error {
func Up20200411164603(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table playlist
add created_at datetime;
@ -22,6 +23,6 @@ update playlist
return err
}
func Down20200411164603(tx *sql.Tx) error {
func Down20200411164603(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,20 +1,21 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200418110522, Down20200418110522)
goose.AddMigrationContext(Up20200418110522, Down20200418110522)
}
func Up20200418110522(tx *sql.Tx) error {
func Up20200418110522(_ context.Context, tx *sql.Tx) error {
notice(tx, "A full rescan will be performed to fix search Albums by year")
return forceFullRescan(tx)
}
func Down20200418110522(tx *sql.Tx) error {
func Down20200418110522(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,20 +1,21 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200419222708, Down20200419222708)
goose.AddMigrationContext(Up20200419222708, Down20200419222708)
}
func Up20200419222708(tx *sql.Tx) error {
func Up20200419222708(_ context.Context, tx *sql.Tx) error {
notice(tx, "A full rescan will be performed to change the search behaviour")
return forceFullRescan(tx)
}
func Down20200419222708(tx *sql.Tx) error {
func Down20200419222708(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200423204116, Down20200423204116)
goose.AddMigrationContext(Up20200423204116, Down20200423204116)
}
func Up20200423204116(tx *sql.Tx) error {
func Up20200423204116(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table artist
add order_artist_name varchar(255) collate nocase;
@ -60,6 +61,6 @@ create index if not exists media_file_order_artist_name
return forceFullRescan(tx)
}
func Down20200423204116(tx *sql.Tx) error {
func Down20200423204116(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200508093059, Down20200508093059)
goose.AddMigrationContext(Up20200508093059, Down20200508093059)
}
func Up20200508093059(tx *sql.Tx) error {
func Up20200508093059(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table artist
add song_count integer default 0 not null;
@ -22,6 +23,6 @@ alter table artist
return forceFullRescan(tx)
}
func Down20200508093059(tx *sql.Tx) error {
func Down20200508093059(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200512104202, Down20200512104202)
goose.AddMigrationContext(Up20200512104202, Down20200512104202)
}
func Up20200512104202(tx *sql.Tx) error {
func Up20200512104202(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table media_file
add disc_subtitle varchar(255);
@ -22,6 +23,6 @@ alter table media_file
return forceFullRescan(tx)
}
func Down20200512104202(tx *sql.Tx) error {
func Down20200512104202(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,6 +1,7 @@
package migrations
import (
"context"
"database/sql"
"strings"
@ -9,10 +10,10 @@ import (
)
func init() {
goose.AddMigration(Up20200516140647, Down20200516140647)
goose.AddMigrationContext(Up20200516140647, Down20200516140647)
}
func Up20200516140647(tx *sql.Tx) error {
func Up20200516140647(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table if not exists playlist_tracks
(
@ -95,6 +96,6 @@ func Up20200516140647UpdatePlaylistTracks(tx *sql.Tx, id string, tracks string)
return nil
}
func Down20200516140647(tx *sql.Tx) error {
func Down20200516140647(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20200608153717, Down20200608153717)
goose.AddMigrationContext(Up20200608153717, Down20200608153717)
}
func Up20200608153717(tx *sql.Tx) error {
func Up20200608153717(_ context.Context, tx *sql.Tx) error {
// First delete dangling players
_, err := tx.Exec(`
delete from player where user_name not in (select user_name from user)`)
@ -132,6 +133,6 @@ create unique index playlist_tracks_pos
return err
}
func Down20200608153717(tx *sql.Tx) error {
func Down20200608153717(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,6 +1,7 @@
package migrations
import (
"context"
"database/sql"
"github.com/google/uuid"
@ -9,10 +10,10 @@ import (
)
func init() {
goose.AddMigration(upAddDefaultTranscodings, downAddDefaultTranscodings)
goose.AddMigrationContext(upAddDefaultTranscodings, downAddDefaultTranscodings)
}
func upAddDefaultTranscodings(tx *sql.Tx) error {
func upAddDefaultTranscodings(_ context.Context, tx *sql.Tx) error {
row := tx.QueryRow("SELECT COUNT(*) FROM transcoding")
var count int
err := row.Scan(&count)
@ -37,6 +38,6 @@ func upAddDefaultTranscodings(tx *sql.Tx) error {
return nil
}
func downAddDefaultTranscodings(tx *sql.Tx) error {
func downAddDefaultTranscodings(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddPlaylistPath, downAddPlaylistPath)
goose.AddMigrationContext(upAddPlaylistPath, downAddPlaylistPath)
}
func upAddPlaylistPath(tx *sql.Tx) error {
func upAddPlaylistPath(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table playlist
add path string default '' not null;
@ -22,6 +23,6 @@ alter table playlist
return err
}
func downAddPlaylistPath(tx *sql.Tx) error {
func downAddPlaylistPath(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upCreatePlayQueuesTable, downCreatePlayQueuesTable)
goose.AddMigrationContext(upCreatePlayQueuesTable, downCreatePlayQueuesTable)
}
func upCreatePlayQueuesTable(tx *sql.Tx) error {
func upCreatePlayQueuesTable(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table playqueue
(
@ -31,6 +32,6 @@ create table playqueue
return err
}
func downCreatePlayQueuesTable(tx *sql.Tx) error {
func downCreatePlayQueuesTable(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upCreateBookmarkTable, downCreateBookmarkTable)
goose.AddMigrationContext(upCreateBookmarkTable, downCreateBookmarkTable)
}
func upCreateBookmarkTable(tx *sql.Tx) error {
func upCreateBookmarkTable(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table bookmark
(
@ -48,6 +49,6 @@ alter table playqueue_dg_tmp rename to playqueue;
return err
}
func downCreateBookmarkTable(tx *sql.Tx) error {
func downCreateBookmarkTable(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upDropEmailUniqueConstraint, downDropEmailUniqueConstraint)
goose.AddMigrationContext(upDropEmailUniqueConstraint, downDropEmailUniqueConstraint)
}
func upDropEmailUniqueConstraint(tx *sql.Tx) error {
func upDropEmailUniqueConstraint(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table user_dg_tmp
(
@ -37,6 +38,6 @@ alter table user_dg_tmp rename to user;
return err
}
func downDropEmailUniqueConstraint(tx *sql.Tx) error {
func downDropEmailUniqueConstraint(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20201003111749, Down20201003111749)
goose.AddMigrationContext(Up20201003111749, Down20201003111749)
}
func Up20201003111749(tx *sql.Tx) error {
func Up20201003111749(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create index if not exists annotation_starred_at
on annotation (starred_at);
@ -18,6 +19,6 @@ create index if not exists annotation_starred_at
return err
}
func Down20201003111749(tx *sql.Tx) error {
func Down20201003111749(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20201010162350, Down20201010162350)
goose.AddMigrationContext(Up20201010162350, Down20201010162350)
}
func Up20201010162350(tx *sql.Tx) error {
func Up20201010162350(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table album
add size integer default 0 not null;
@ -27,7 +28,7 @@ where id not null;`)
return err
}
func Down20201010162350(tx *sql.Tx) error {
func Down20201010162350(_ context.Context, tx *sql.Tx) error {
// This code is executed when the migration is rolled back.
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20201012210022, Down20201012210022)
goose.AddMigrationContext(Up20201012210022, Down20201012210022)
}
func Up20201012210022(tx *sql.Tx) error {
func Up20201012210022(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table artist
add size integer default 0 not null;
@ -39,6 +40,6 @@ update playlist set size = ifnull((
return err
}
func Down20201012210022(tx *sql.Tx) error {
func Down20201012210022(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20201021085410, Down20201021085410)
goose.AddMigrationContext(Up20201021085410, Down20201021085410)
}
func Up20201021085410(tx *sql.Tx) error {
func Up20201021085410(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table media_file
add mbz_track_id varchar(255);
@ -52,7 +53,7 @@ alter table artist
return forceFullRescan(tx)
}
func Down20201021085410(tx *sql.Tx) error {
func Down20201021085410(_ context.Context, tx *sql.Tx) error {
// This code is executed when the migration is rolled back.
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20201021093209, Down20201021093209)
goose.AddMigrationContext(Up20201021093209, Down20201021093209)
}
func Up20201021093209(tx *sql.Tx) error {
func Up20201021093209(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create index if not exists media_file_artist
on media_file (artist);
@ -22,6 +23,6 @@ create index if not exists media_file_mbz_track_id
return err
}
func Down20201021093209(tx *sql.Tx) error {
func Down20201021093209(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20201021135455, Down20201021135455)
goose.AddMigrationContext(Up20201021135455, Down20201021135455)
}
func Up20201021135455(tx *sql.Tx) error {
func Up20201021135455(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create index if not exists media_file_artist_id
on media_file (artist_id);
@ -18,6 +19,6 @@ create index if not exists media_file_artist_id
return err
}
func Down20201021135455(tx *sql.Tx) error {
func Down20201021135455(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddArtistImageUrl, downAddArtistImageUrl)
goose.AddMigrationContext(upAddArtistImageUrl, downAddArtistImageUrl)
}
func upAddArtistImageUrl(tx *sql.Tx) error {
func upAddArtistImageUrl(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table artist
add biography varchar(255) default '' not null;
@ -30,6 +31,6 @@ alter table artist
return err
}
func downAddArtistImageUrl(tx *sql.Tx) error {
func downAddArtistImageUrl(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20201110205344, Down20201110205344)
goose.AddMigrationContext(Up20201110205344, Down20201110205344)
}
func Up20201110205344(tx *sql.Tx) error {
func Up20201110205344(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table media_file
add comment varchar;
@ -27,6 +28,6 @@ alter table album
return forceFullRescan(tx)
}
func Down20201110205344(tx *sql.Tx) error {
func Down20201110205344(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up20201128100726, Down20201128100726)
goose.AddMigrationContext(Up20201128100726, Down20201128100726)
}
func Up20201128100726(tx *sql.Tx) error {
func Up20201128100726(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table player
add report_real_path bool default FALSE not null;
@ -18,6 +19,6 @@ alter table player
return err
}
func Down20201128100726(tx *sql.Tx) error {
func Down20201128100726(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,6 +1,7 @@
package migrations
import (
"context"
"database/sql"
"github.com/navidrome/navidrome/log"
@ -9,10 +10,10 @@ import (
)
func init() {
goose.AddMigration(Up20201213124814, Down20201213124814)
goose.AddMigrationContext(Up20201213124814, Down20201213124814)
}
func Up20201213124814(tx *sql.Tx) error {
func Up20201213124814(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table album
add all_artist_ids varchar;
@ -58,6 +59,6 @@ select a.id, a.name, a.artist_id, a.album_artist_id, group_concat(mf.artist_id,
return rows.Err()
}
func Down20201213124814(tx *sql.Tx) error {
func Down20201213124814(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddTimestampIndexesGo, downAddTimestampIndexesGo)
goose.AddMigrationContext(upAddTimestampIndexesGo, downAddTimestampIndexesGo)
}
func upAddTimestampIndexesGo(tx *sql.Tx) error {
func upAddTimestampIndexesGo(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create index if not exists album_updated_at
on album (updated_at);
@ -28,6 +29,6 @@ create index if not exists media_file_updated_at
return err
}
func downAddTimestampIndexesGo(tx *sql.Tx) error {
func downAddTimestampIndexesGo(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,6 +1,7 @@
package migrations
import (
"context"
"database/sql"
"strings"
@ -10,10 +11,10 @@ import (
)
func init() {
goose.AddMigration(upFixAlbumComments, downFixAlbumComments)
goose.AddMigrationContext(upFixAlbumComments, downFixAlbumComments)
}
func upFixAlbumComments(tx *sql.Tx) error {
func upFixAlbumComments(_ context.Context, tx *sql.Tx) error {
//nolint:gosec
rows, err := tx.Query(`
SELECT album.id, group_concat(media_file.comment, '` + consts.Zwsp + `') FROM album, media_file WHERE media_file.album_id = album.id GROUP BY album.id;
@ -48,7 +49,7 @@ func upFixAlbumComments(tx *sql.Tx) error {
return rows.Err()
}
func downFixAlbumComments(tx *sql.Tx) error {
func downFixAlbumComments(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddBpmMetadata, downAddBpmMetadata)
goose.AddMigrationContext(upAddBpmMetadata, downAddBpmMetadata)
}
func upAddBpmMetadata(tx *sql.Tx) error {
func upAddBpmMetadata(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table media_file
add bpm integer;
@ -25,6 +26,6 @@ create index if not exists media_file_bpm
return forceFullRescan(tx)
}
func downAddBpmMetadata(tx *sql.Tx) error {
func downAddBpmMetadata(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upCreateSharesTable, downCreateSharesTable)
goose.AddMigrationContext(upCreateSharesTable, downCreateSharesTable)
}
func upCreateSharesTable(tx *sql.Tx) error {
func upCreateSharesTable(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table share
(
@ -29,6 +30,6 @@ create table share
return err
}
func downCreateSharesTable(tx *sql.Tx) error {
func downCreateSharesTable(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upUpdateShareFieldNames, downUpdateShareFieldNames)
goose.AddMigrationContext(upUpdateShareFieldNames, downUpdateShareFieldNames)
}
func upUpdateShareFieldNames(tx *sql.Tx) error {
func upUpdateShareFieldNames(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table share rename column expires to expires_at;
alter table share rename column created to created_at;
@ -20,6 +21,6 @@ alter table share rename column last_visited to last_visited_at;
return err
}
func downUpdateShareFieldNames(tx *sql.Tx) error {
func downUpdateShareFieldNames(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -12,10 +12,10 @@ import (
)
func init() {
goose.AddMigration(upEncodeAllPasswords, downEncodeAllPasswords)
goose.AddMigrationContext(upEncodeAllPasswords, downEncodeAllPasswords)
}
func upEncodeAllPasswords(tx *sql.Tx) error {
func upEncodeAllPasswords(ctx context.Context, tx *sql.Tx) error {
rows, err := tx.Query(`SELECT id, user_name, password from user;`)
if err != nil {
return err
@ -38,7 +38,7 @@ func upEncodeAllPasswords(tx *sql.Tx) error {
return err
}
password, err = utils.Encrypt(context.Background(), encKey, password)
password, err = utils.Encrypt(ctx, encKey, password)
if err != nil {
log.Error("Error encrypting user's password", "id", id, "username", username, err)
}
@ -51,6 +51,6 @@ func upEncodeAllPasswords(tx *sql.Tx) error {
return rows.Err()
}
func downEncodeAllPasswords(tx *sql.Tx) error {
func downEncodeAllPasswords(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upDropPlayerNameUniqueConstraint, downDropPlayerNameUniqueConstraint)
goose.AddMigrationContext(upDropPlayerNameUniqueConstraint, downDropPlayerNameUniqueConstraint)
}
func upDropPlayerNameUniqueConstraint(tx *sql.Tx) error {
func upDropPlayerNameUniqueConstraint(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table player_dg_tmp
(
@ -42,6 +43,6 @@ create index if not exists player_name
return err
}
func downDropPlayerNameUniqueConstraint(tx *sql.Tx) error {
func downDropPlayerNameUniqueConstraint(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddUserPrefsPlayerScrobblerEnabled, downAddUserPrefsPlayerScrobblerEnabled)
goose.AddMigrationContext(upAddUserPrefsPlayerScrobblerEnabled, downAddUserPrefsPlayerScrobblerEnabled)
}
func upAddUserPrefsPlayerScrobblerEnabled(tx *sql.Tx) error {
func upAddUserPrefsPlayerScrobblerEnabled(_ context.Context, tx *sql.Tx) error {
err := upAddUserPrefs(tx)
if err != nil {
return err
@ -39,6 +40,6 @@ alter table player add scrobble_enabled bool default true;
return err
}
func downAddUserPrefsPlayerScrobblerEnabled(tx *sql.Tx) error {
func downAddUserPrefsPlayerScrobblerEnabled(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddReferentialIntegrityToUserProps, downAddReferentialIntegrityToUserProps)
goose.AddMigrationContext(upAddReferentialIntegrityToUserProps, downAddReferentialIntegrityToUserProps)
}
func upAddReferentialIntegrityToUserProps(tx *sql.Tx) error {
func upAddReferentialIntegrityToUserProps(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table user_props_dg_tmp
(
@ -33,6 +34,6 @@ alter table user_props_dg_tmp rename to user_props;
return err
}
func downAddReferentialIntegrityToUserProps(tx *sql.Tx) error {
func downAddReferentialIntegrityToUserProps(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddScrobbleBuffer, downAddScrobbleBuffer)
goose.AddMigrationContext(upAddScrobbleBuffer, downAddScrobbleBuffer)
}
func upAddScrobbleBuffer(tx *sql.Tx) error {
func upAddScrobbleBuffer(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table if not exists scrobble_buffer
(
@ -33,6 +34,6 @@ create table if not exists scrobble_buffer
return err
}
func downAddScrobbleBuffer(tx *sql.Tx) error {
func downAddScrobbleBuffer(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddGenreTables, downAddGenreTables)
goose.AddMigrationContext(upAddGenreTables, downAddGenreTables)
}
func upAddGenreTables(tx *sql.Tx) error {
func upAddGenreTables(_ context.Context, tx *sql.Tx) error {
notice(tx, "A full rescan will be performed to import multiple genres!")
_, err := tx.Exec(`
create table if not exists genre
@ -63,6 +64,6 @@ create table if not exists artist_genres
return forceFullRescan(tx)
}
func downAddGenreTables(tx *sql.Tx) error {
func downAddGenreTables(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddMediafileChannels, downAddMediafileChannels)
goose.AddMigrationContext(upAddMediafileChannels, downAddMediafileChannels)
}
func upAddMediafileChannels(tx *sql.Tx) error {
func upAddMediafileChannels(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table media_file
add channels integer;
@ -25,6 +26,6 @@ create index if not exists media_file_channels
return forceFullRescan(tx)
}
func downAddMediafileChannels(tx *sql.Tx) error {
func downAddMediafileChannels(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddSmartPlaylist, downAddSmartPlaylist)
goose.AddMigrationContext(upAddSmartPlaylist, downAddSmartPlaylist)
}
func upAddSmartPlaylist(tx *sql.Tx) error {
func upAddSmartPlaylist(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table playlist
add column rules varchar null;
@ -32,6 +33,6 @@ create unique index playlist_fields_idx
return err
}
func downAddSmartPlaylist(tx *sql.Tx) error {
func downAddSmartPlaylist(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,6 +1,7 @@
package migrations
import (
"context"
"database/sql"
"strings"
@ -10,10 +11,10 @@ import (
)
func init() {
goose.AddMigration(upAddOrderTitleToMediaFile, downAddOrderTitleToMediaFile)
goose.AddMigrationContext(upAddOrderTitleToMediaFile, downAddOrderTitleToMediaFile)
}
func upAddOrderTitleToMediaFile(tx *sql.Tx) error {
func upAddOrderTitleToMediaFile(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table main.media_file
add order_title varchar null collate NOCASE;
@ -56,6 +57,6 @@ func upAddOrderTitleToMediaFile_populateOrderTitle(tx *sql.Tx) error {
return rows.Err()
}
func downAddOrderTitleToMediaFile(tx *sql.Tx) error {
func downAddOrderTitleToMediaFile(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,6 +1,7 @@
package migrations
import (
"context"
"database/sql"
"github.com/navidrome/navidrome/log"
@ -9,10 +10,10 @@ import (
)
func init() {
goose.AddMigration(upUnescapeLyricsAndComments, downUnescapeLyricsAndComments)
goose.AddMigrationContext(upUnescapeLyricsAndComments, downUnescapeLyricsAndComments)
}
func upUnescapeLyricsAndComments(tx *sql.Tx) error {
func upUnescapeLyricsAndComments(_ context.Context, tx *sql.Tx) error {
rows, err := tx.Query(`select id, comment, lyrics, title from media_file`)
if err != nil {
return err
@ -42,6 +43,6 @@ func upUnescapeLyricsAndComments(tx *sql.Tx) error {
return rows.Err()
}
func downUnescapeLyricsAndComments(tx *sql.Tx) error {
func downUnescapeLyricsAndComments(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddUseridToPlaylist, downAddUseridToPlaylist)
goose.AddMigrationContext(upAddUseridToPlaylist, downAddUseridToPlaylist)
}
func upAddUseridToPlaylist(tx *sql.Tx) error {
func upAddUseridToPlaylist(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table playlist_dg_tmp
(
@ -55,6 +56,6 @@ create index playlist_updated_at
return err
}
func downAddUseridToPlaylist(tx *sql.Tx) error {
func downAddUseridToPlaylist(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddAlphabeticalByArtistIndex, downAddAlphabeticalByArtistIndex)
goose.AddMigrationContext(upAddAlphabeticalByArtistIndex, downAddAlphabeticalByArtistIndex)
}
func upAddAlphabeticalByArtistIndex(tx *sql.Tx) error {
func upAddAlphabeticalByArtistIndex(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create index album_alphabetical_by_artist
ON album(compilation, order_album_artist_name, order_album_name)
@ -18,6 +19,6 @@ create index album_alphabetical_by_artist
return err
}
func downAddAlphabeticalByArtistIndex(tx *sql.Tx) error {
func downAddAlphabeticalByArtistIndex(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,22 +1,23 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upRemoveInvalidArtistIds, downRemoveInvalidArtistIds)
goose.AddMigrationContext(upRemoveInvalidArtistIds, downRemoveInvalidArtistIds)
}
func upRemoveInvalidArtistIds(tx *sql.Tx) error {
func upRemoveInvalidArtistIds(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
update media_file set artist_id = '' where not exists(select 1 from artist where id = artist_id)
`)
return err
}
func downRemoveInvalidArtistIds(tx *sql.Tx) error {
func downRemoveInvalidArtistIds(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddMusicbrainzReleaseTrackId, downAddMusicbrainzReleaseTrackId)
goose.AddMigrationContext(upAddMusicbrainzReleaseTrackId, downAddMusicbrainzReleaseTrackId)
}
func upAddMusicbrainzReleaseTrackId(tx *sql.Tx) error {
func upAddMusicbrainzReleaseTrackId(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table media_file
add mbz_release_track_id varchar(255);
@ -22,7 +23,7 @@ alter table media_file
return forceFullRescan(tx)
}
func downAddMusicbrainzReleaseTrackId(tx *sql.Tx) error {
func downAddMusicbrainzReleaseTrackId(_ context.Context, tx *sql.Tx) error {
// This code is executed when the migration is rolled back.
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddAlbumImagePaths, downAddAlbumImagePaths)
goose.AddMigrationContext(upAddAlbumImagePaths, downAddAlbumImagePaths)
}
func upAddAlbumImagePaths(tx *sql.Tx) error {
func upAddAlbumImagePaths(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table main.album add image_files varchar;
`)
@ -21,6 +22,6 @@ alter table main.album add image_files varchar;
return forceFullRescan(tx)
}
func downAddAlbumImagePaths(tx *sql.Tx) error {
func downAddAlbumImagePaths(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upRemoveCoverArtId, downRemoveCoverArtId)
goose.AddMigrationContext(upRemoveCoverArtId, downRemoveCoverArtId)
}
func upRemoveCoverArtId(tx *sql.Tx) error {
func upRemoveCoverArtId(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table album drop column cover_art_id;
alter table album rename column cover_art_path to embed_art_path
@ -22,6 +23,6 @@ alter table album rename column cover_art_path to embed_art_path
return forceFullRescan(tx)
}
func downRemoveCoverArtId(tx *sql.Tx) error {
func downRemoveCoverArtId(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,6 +1,7 @@
package migrations
import (
"context"
"database/sql"
"path/filepath"
"strings"
@ -12,10 +13,10 @@ import (
)
func init() {
goose.AddMigration(upAddAlbumPaths, downAddAlbumPaths)
goose.AddMigrationContext(upAddAlbumPaths, downAddAlbumPaths)
}
func upAddAlbumPaths(tx *sql.Tx) error {
func upAddAlbumPaths(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`alter table album add paths varchar;`)
if err != nil {
return err
@ -62,6 +63,6 @@ func upAddAlbumPathsDirs(filePaths string) string {
return strings.Join(dirs, string(filepath.ListSeparator))
}
func downAddAlbumPaths(tx *sql.Tx) error {
func downAddAlbumPaths(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,20 +1,21 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upTouchPlaylists, downTouchPlaylists)
goose.AddMigrationContext(upTouchPlaylists, downTouchPlaylists)
}
func upTouchPlaylists(tx *sql.Tx) error {
func upTouchPlaylists(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`update playlist set updated_at = datetime('now');`)
return err
}
func downTouchPlaylists(tx *sql.Tx) error {
func downTouchPlaylists(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upCreateInternetRadio, downCreateInternetRadio)
goose.AddMigrationContext(upCreateInternetRadio, downCreateInternetRadio)
}
func upCreateInternetRadio(tx *sql.Tx) error {
func upCreateInternetRadio(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
create table if not exists radio
(
@ -25,6 +26,6 @@ create table if not exists radio
return err
}
func downCreateInternetRadio(tx *sql.Tx) error {
func downCreateInternetRadio(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddReplaygainMetadata, downAddReplaygainMetadata)
goose.AddMigrationContext(upAddReplaygainMetadata, downAddReplaygainMetadata)
}
func upAddReplaygainMetadata(tx *sql.Tx) error {
func upAddReplaygainMetadata(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table media_file add
rg_album_gain real;
@ -29,6 +30,6 @@ alter table media_file add
return forceFullRescan(tx)
}
func downAddReplaygainMetadata(tx *sql.Tx) error {
func downAddReplaygainMetadata(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddAlbumInfo, downAddAlbumInfo)
goose.AddMigrationContext(upAddAlbumInfo, downAddAlbumInfo)
}
func upAddAlbumInfo(tx *sql.Tx) error {
func upAddAlbumInfo(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table album
add description varchar(255) default '' not null;
@ -28,6 +29,6 @@ alter table album
return err
}
func downAddAlbumInfo(tx *sql.Tx) error {
func downAddAlbumInfo(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddMissingShareInfo, downAddMissingShareInfo)
goose.AddMigrationContext(upAddMissingShareInfo, downAddMissingShareInfo)
}
func upAddMissingShareInfo(tx *sql.Tx) error {
func upAddMissingShareInfo(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
drop table if exists share;
create table share
@ -36,6 +37,6 @@ create table share
return err
}
func downAddMissingShareInfo(tx *sql.Tx) error {
func downAddMissingShareInfo(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,6 +1,7 @@
package migrations
import (
"context"
"database/sql"
"path/filepath"
"strings"
@ -12,10 +13,10 @@ import (
)
func init() {
goose.AddMigration(upChangePathListSeparator, downChangePathListSeparator)
goose.AddMigrationContext(upChangePathListSeparator, downChangePathListSeparator)
}
func upChangePathListSeparator(tx *sql.Tx) error {
func upChangePathListSeparator(_ context.Context, tx *sql.Tx) error {
//nolint:gosec
rows, err := tx.Query(`
select album_id, group_concat(path, '` + consts.Zwsp + `') from media_file group by album_id
@ -57,6 +58,6 @@ func upChangePathListSeparatorDirs(filePaths string) string {
return strings.Join(dirs, consts.Zwsp)
}
func downChangePathListSeparator(tx *sql.Tx) error {
func downChangePathListSeparator(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,6 +1,7 @@
package migrations
import (
"context"
"database/sql"
"path/filepath"
"strings"
@ -12,11 +13,10 @@ import (
)
func init() {
goose.AddMigration(upChangeImageFilesListSeparator, downChangeImageFilesListSeparator)
goose.AddMigrationContext(upChangeImageFilesListSeparator, downChangeImageFilesListSeparator)
}
func upChangeImageFilesListSeparator(tx *sql.Tx) error {
//nolint:gosec
func upChangeImageFilesListSeparator(_ context.Context, tx *sql.Tx) error {
rows, err := tx.Query(`select id, image_files from album`)
if err != nil {
return err
@ -54,7 +54,7 @@ func upChangeImageFilesListSeparatorDirs(filePaths string) string {
return strings.Join(allPaths, consts.Zwsp)
}
func downChangeImageFilesListSeparator(tx *sql.Tx) error {
func downChangeImageFilesListSeparator(_ context.Context, tx *sql.Tx) error {
// This code is executed when the migration is rolled back.
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddDownloadToShare, downAddDownloadToShare)
goose.AddMigrationContext(upAddDownloadToShare, downAddDownloadToShare)
}
func upAddDownloadToShare(tx *sql.Tx) error {
func upAddDownloadToShare(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table share
add downloadable bool not null default false;
@ -18,6 +19,6 @@ alter table share
return err
}
func downAddDownloadToShare(tx *sql.Tx) error {
func downAddDownloadToShare(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddRelRecYear, downAddRelRecYear)
goose.AddMigrationContext(upAddRelRecYear, downAddRelRecYear)
}
func upAddRelRecYear(tx *sql.Tx) error {
func upAddRelRecYear(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table media_file
add date varchar(255) default '' not null;
@ -44,6 +45,6 @@ alter table album
return forceFullRescan(tx)
}
func downAddRelRecYear(tx *sql.Tx) error {
func downAddRelRecYear(_ context.Context, tx *sql.Tx) error {
return nil
}

View file

@ -1,16 +1,17 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upRenameMusicbrainzRecordingId, downRenameMusicbrainzRecordingId)
goose.AddMigrationContext(upRenameMusicbrainzRecordingId, downRenameMusicbrainzRecordingId)
}
func upRenameMusicbrainzRecordingId(tx *sql.Tx) error {
func upRenameMusicbrainzRecordingId(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table media_file
rename column mbz_track_id to mbz_recording_id;
@ -18,7 +19,7 @@ alter table media_file
return err
}
func downRenameMusicbrainzRecordingId(tx *sql.Tx) error {
func downRenameMusicbrainzRecordingId(_ context.Context, tx *sql.Tx) error {
_, err := tx.Exec(`
alter table media_file
rename column mbz_recording_id to mbz_track_id;

View file

@ -10,7 +10,7 @@
#
# This script does not handle file names that contain spaces.
gofmtcmd="go run golang.org/x/tools/cmd/goimports"
gofmtcmd="go run golang.org/x/tools/cmd/goimports@latest"
gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$' | grep -v '_gen.go$')
[ -z "$gofiles" ] && exit 0

7
go.mod
View file

@ -36,7 +36,7 @@ require (
github.com/mileusna/useragent v1.3.4
github.com/onsi/ginkgo/v2 v2.13.1
github.com/onsi/gomega v1.30.0
github.com/pressly/goose/v3 v3.11.2
github.com/pressly/goose/v3 v3.15.1
github.com/prometheus/client_golang v1.17.0
github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.9.3
@ -49,7 +49,6 @@ require (
golang.org/x/image v0.14.0
golang.org/x/sync v0.5.0
golang.org/x/text v0.14.0
golang.org/x/tools v0.15.0
gopkg.in/yaml.v3 v3.0.1
)
@ -92,7 +91,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.1 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
@ -107,9 +106,9 @@ require (
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/exp/shiny v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
golang.org/x/mobile v0.0.0-20230427221453-e8d11dd0ba41 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/tools v0.15.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect

40
go.sum
View file

@ -270,8 +270,8 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v
github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
@ -302,8 +302,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pressly/goose/v3 v3.11.2 h1:QgTP45FhBBHdmf7hWKlbWFHtwPtxo0phSDkwDKGUrYs=
github.com/pressly/goose/v3 v3.11.2/go.mod h1:LWQzSc4vwfHA/3B8getTp8g3J5Z8tFBxgxinmGlMlJk=
github.com/pressly/goose/v3 v3.15.1 h1:dKaJ1SdLvS/+HtS8PzFT0KBEtICC1jewLXM+b3emlv8=
github.com/pressly/goose/v3 v3.15.1/go.mod h1:0E3Yg/+EwYzO6Rz2P98MlClFgIcoujbVRs575yi3iIM=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -311,8 +311,8 @@ github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLq
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
@ -746,22 +746,22 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q=
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0=
modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI=
modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.22.1 h1:P2+Dhp5FR1RlVRkQ3dDfCiv3Ok8XPxqpe70IjYVA9oE=
modernc.org/sqlite v1.22.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/sqlite v1.26.0 h1:SocQdLRSYlA8W99V8YH0NES75thx19d9sB/aFc4R8Lw=
modernc.org/sqlite v1.26.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View file

@ -91,7 +91,7 @@ func (r *mediaFileRepository) Get(id string) (*model.MediaFile, error) {
func (r *mediaFileRepository) GetAll(options ...model.QueryOptions) (model.MediaFiles, error) {
sq := r.selectMediaFile(options...)
res := model.MediaFiles{}
err := r.queryAll(sq, &res)
err := r.queryAll(sq, &res, options...)
if err != nil {
return nil, err
}

View file

@ -19,7 +19,13 @@ func (r sqlRepository) newSelectWithAnnotation(idField string, options ...model.
"annotation.item_id = "+idField+
" AND annotation.item_type = '"+r.tableName+"'"+
" AND annotation.user_id = '"+userId(r.ctx)+"')").
Columns("starred", "starred_at", "play_count", "play_date", "rating")
Columns(
"coalesce(starred, 0) as starred",
"coalesce(rating, 0) as rating",
"coalesce(play_count, 0) as play_count",
"starred_at",
"play_date",
)
}
func (r sqlRepository) annId(itemID ...string) And {

View file

@ -10,6 +10,7 @@ import (
. "github.com/Masterminds/squirrel"
"github.com/beego/beego/v2/client/orm"
"github.com/google/uuid"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
@ -157,7 +158,10 @@ func (r sqlRepository) queryOne(sq Sqlizer, response interface{}) error {
return err
}
func (r sqlRepository) queryAll(sq Sqlizer, response interface{}) error {
func (r sqlRepository) queryAll(sq SelectBuilder, response interface{}, options ...model.QueryOptions) error {
if len(options) > 0 && options[0].Offset > 0 {
sq = r.optimizePagination(sq, options[0])
}
query, args, err := sq.ToSql()
if err != nil {
return err
@ -172,6 +176,19 @@ func (r sqlRepository) queryAll(sq Sqlizer, response interface{}) error {
return err
}
// optimizePagination uses a less inefficient pagination, by not using OFFSET.
// See https://gist.github.com/ssokolow/262503
func (r sqlRepository) optimizePagination(sq SelectBuilder, options model.QueryOptions) SelectBuilder {
if options.Offset > conf.Server.DevOffsetOptimize {
sq = sq.RemoveOffset()
oidSq := sq.RemoveColumns().Columns(r.tableName + ".oid")
oidSq = oidSq.Limit(uint64(options.Offset))
oidSql, args, _ := oidSq.ToSql()
sq = sq.Where(r.tableName+".oid not in ("+oidSql+")", args...)
}
return sq
}
func (r sqlRepository) exists(existsQuery SelectBuilder) (bool, error) {
existsQuery = existsQuery.Columns("count(*) as exist").From(r.tableName)
var res struct{ Exist int64 }

View file

@ -104,6 +104,11 @@ func (r sqlRepository) GetBookmarks() (model.Bookmarks, error) {
log.Error(r.ctx, "Error getting mediafiles with bookmarks", "user", user.UserName, err)
return nil, err
}
err = r.loadMediaFileGenres(&mfs)
if err != nil {
log.Error(r.ctx, "Error loading genres for bookmarked songs", "user", user.UserName, err)
return nil, err
}
ids := make([]string, len(mfs))
mfMap := make(map[string]int)

View file

@ -5,6 +5,7 @@ import (
. "github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils"
)
@ -21,21 +22,31 @@ func (r sqlRepository) doSearch(q string, offset, size int, results interface{},
}
sq := r.newSelectWithAnnotation(r.tableName + ".id").Columns("*")
sq = sq.Limit(uint64(size)).Offset(uint64(offset))
if len(orderBys) > 0 {
sq = sq.OrderBy(orderBys...)
filter := fullTextExpr(q)
if filter != nil {
sq = sq.Where(filter)
if len(orderBys) > 0 {
sq = sq.OrderBy(orderBys...)
}
} else {
// If the filter is empty, we sort by id.
// This is to speed up the results of `search3?query=""`, for OpenSubsonic
sq = sq.OrderBy("id")
}
sq = sq.Where(fullTextExpr(q))
err := r.queryAll(sq, results)
sq = sq.Limit(uint64(size)).Offset(uint64(offset))
err := r.queryAll(sq, results, model.QueryOptions{Offset: offset})
return err
}
func fullTextExpr(value string) Sqlizer {
q := utils.SanitizeStrings(value)
if q == "" {
return nil
}
var sep string
if !conf.Server.SearchFullString {
sep = " "
}
q := utils.SanitizeStrings(value)
parts := strings.Split(q, " ")
filters := And{}
for _, part := range parts {

View file

@ -182,7 +182,7 @@
}
},
"share": {
"name": "Compartición ||| Comparticións",
"name": "Compartido |||| Compartidos",
"fields": {
"username": "Compartida por",
"url": "URL",

View file

@ -18,6 +18,7 @@ import (
)
const ENCODED_LYRICS_KEY = "__navidrome__lyrics"
const NAVIDROME_SYNCHRONIZED_KEY = "__navidrome__synched__lyrics"
type Extractor interface {
Parse(files ...string) (map[string]ParsedTags, error)
@ -110,7 +111,7 @@ func (t Tags) Lyrics() string {
result := ""
if t.getFirstTagValue(ENCODED_LYRICS_KEY) == "1" {
tags := t.getAllTagValues("lyrics")
tags := t.getAllTagValues("lyrics", NAVIDROME_SYNCHRONIZED_KEY)
for i := 0; i < len(tags); i += 2 {
result += fmt.Sprintf("%s\u200b%s\u200b", tags[i], tags[i+1])
@ -128,7 +129,7 @@ func (t Tags) Lyrics() string {
}
return result
}
func (t Tags) Compilation() bool { return t.getBool("tcmp", "compilation") }
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") }
func (t Tags) DiscSubtitle() string {

View file

@ -22,9 +22,9 @@ var _ = Describe("Tags", func() {
})
It("correctly parses metadata from all files in folder", func() {
mds, err := metadata.Extract("tests/fixtures/test.mp3", "tests/fixtures/test.ogg")
mds, err := metadata.Extract("tests/fixtures/test.mp3", "tests/fixtures/test.ogg", "tests/fixtures/test.wma")
Expect(err).NotTo(HaveOccurred())
Expect(mds).To(HaveLen(2))
Expect(mds).To(HaveLen(3))
m := mds["tests/fixtures/test.mp3"]
Expect(m.Title()).To(Equal("Song"))
@ -71,6 +71,17 @@ var _ = Describe("Tags", func() {
// 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, 43, 49))
m = mds["tests/fixtures/test.wma"]
Expect(err).To(BeNil())
Expect(m.Compilation()).To(BeTrue())
Expect(m.Title()).To(Equal("Title"))
Expect(m.HasPicture()).To(BeFalse())
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(21581)))
Expect(m.BitRate()).To(BeElementOf(128))
})
DescribeTable("Lyrics test",
@ -87,18 +98,30 @@ var _ = Describe("Tags", func() {
} else {
lyrics = strings.Join([]string{"xxx", unspecifiedLyric, "xxx", englishLyric}, consts.Zwsp)
}
println(m.Lyrics())
Expect(m.Lyrics()).To(Equal(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 MP3 files", "test.mp3", true),
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]
lyrics := strings.Join([]string{"eng", englishLyric, "xxx", unspecifiedLyric, "eng", englishLyric + "\n", "xxx", unspecifiedLyric + "\n"}, consts.Zwsp)
Expect(m.Lyrics()).To(Equal(lyrics))
})
})
Context("Extract", func() {

View file

@ -40,7 +40,7 @@ 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(HaveKeyWithValue("compilation", []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"}))
@ -57,6 +57,12 @@ var _ = Describe("Extractor", func() {
"xxx",
"[00:00.00]This is\n[00:02.50]unspecified",
}))
Expect(m).To(HaveKeyWithValue(metadata.NAVIDROME_SYNCHRONIZED_KEY, []string{
"eng",
"[00:00.00]This is\n[00:02.50]English\n",
"xxx",
"[00:00.00]This is\n[00:02.50]unspecified\n",
}))
Expect(m).To(HaveKeyWithValue(metadata.ENCODED_LYRICS_KEY, []string{"1"}))
Expect(m).To(HaveKeyWithValue("bpm", []string{"123"}))
Expect(m).To(HaveKeyWithValue("replaygain_album_gain", []string{"+3.21518 dB"}))
@ -66,7 +72,7 @@ var _ = Describe("Extractor", func() {
Expect(m).To(HaveKeyWithValue("tracknumber", []string{"2/10"}))
m = m.Map(e.CustomMappings())
Expect(m).To(HaveKeyWithValue("tracknumber", []string{"2/10", "2/10", "2"}))
Expect(m).To(HaveKeyWithValue("tracknumber", []string{"2/10", "2"}))
m = mds["tests/fixtures/test.ogg"]
Expect(err).To(BeNil())
@ -79,7 +85,6 @@ var _ = Describe("Extractor", func() {
Expect(m).To(HaveKey("bitrate"))
Expect(m["bitrate"][0]).To(BeElementOf("18", "39", "40", "43", "49"))
})
DescribeTable("Format-Specific tests",
func(file, duration, channels, albumGain, albumPeak, trackGain, trackPeak string, id3Lyrics bool) {
file = "tests/fixtures/" + file
@ -98,15 +103,24 @@ 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("compilation", []string{"1"}))
Expect(m).To(HaveKeyWithValue("genre", []string{"Rock"}))
Expect(m).To(HaveKeyWithValue("date", []string{"2014", "2014"}))
// Special for M4A, do not catch keys that have no actual name
Expect(m).ToNot(HaveKey(""))
Expect(m).To(HaveKey("discnumber"))
discno := m["discnumber"]
Expect(discno).To(HaveLen(1))
Expect(discno[0]).To(BeElementOf([]string{"1", "1/2"}))
// WMA does not have a "compilation" tag, but "wm/iscompilation"
if _, ok := m["compilation"]; ok {
Expect(m).To(HaveKeyWithValue("compilation", []string{"1"}))
} else {
Expect(m).To(HaveKeyWithValue("wm/iscompilation", []string{"1"}))
}
Expect(m).NotTo(HaveKeyWithValue("has_picture", []string{"true"}))
Expect(m).To(HaveKeyWithValue("duration", []string{duration}))
@ -141,6 +155,7 @@ var _ = Describe("Extractor", func() {
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", 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", false),

View file

@ -19,13 +19,6 @@
#include "taglib_wrapper.h"
// Tags necessary for M4a parsing
const char *RG_TAGS[] = {
"replaygain_album_gain",
"replaygain_album_peak",
"replaygain_track_gain",
"replaygain_track_peak"};
char *LYRICS_KEY = (char *) "lyrics";
char *CUSTOM_LYRICS = (char *) "__navidrome__lyrics";
@ -49,6 +42,7 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) {
go_map_put_int(id, (char *)"bitrate", props->bitrate());
go_map_put_int(id, (char *)"channels", props->channels());
// Create a map to collect all the tags
TagLib::PropertyMap tags = f.file()->properties();
// Make sure at least the basic properties are extracted
@ -98,104 +92,113 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) {
if (id3Tags != NULL) {
const auto &frameListMap(id3Tags->frameListMap());
bool hasLyrics = false;
const auto usltList = id3Tags->frameList("USLT");
if (!usltList.isEmpty()) {
for (const auto &tag: usltList) {
TagLib::ID3v2::UnsynchronizedLyricsFrame *frame = dynamic_cast<TagLib::ID3v2::UnsynchronizedLyricsFrame *>(tag);
if (frame == NULL) continue;
for (const auto &kv : frameListMap) {
if (!kv.second.isEmpty())
if (kv.first == "USLT") {
bool hasLyrics = false;
hasLyrics = true;
for (const auto &uslt: kv.second) {
TagLib::ID3v2::UnsynchronizedLyricsFrame *frame = dynamic_cast<TagLib::ID3v2::UnsynchronizedLyricsFrame *>(uslt);
if (frame == NULL) continue;
char lang[4];
strncpy(lang, frame->language().data(), 3);
lang[3] = '\0';
char *val = (char *)frame->text().toCString(true);
char lang[4];
strncpy(lang, frame->language().data(), 3);
lang[3] = '\0';
char *val = ::strdup(frame->text().toCString(true));
go_map_put_str(id, LYRICS_KEY, lang);
go_map_put_str(id, LYRICS_KEY, val);
}
go_map_put_str(id, LYRICS_KEY, lang);
go_map_put_str(id, LYRICS_KEY, val);
tags.erase("LYRICS");
if (hasLyrics) {
tags.erase("LYRICS");
}
}
const auto syltList = id3Tags->frameList("SYLT");
if (!syltList.isEmpty()) {
for (const auto &tag: syltList) {
TagLib::ID3v2::SynchronizedLyricsFrame *frame = dynamic_cast<TagLib::ID3v2::SynchronizedLyricsFrame *>(tag);
if (frame == NULL) continue;
char lang[4];
strncpy(lang, frame->language().data(), 3);
lang[3] = '\0';
const auto format = frame->timestampFormat();
if (format == TagLib::ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds) {
hasLyrics = true;
for (const auto &line: frame->synchedText()) {
char *text = (char *)line.text.toCString(true);
go_map_put_lyric_line(id, lang, text, line.time);
}
} else if (format == TagLib::ID3v2::SynchronizedLyricsFrame::AbsoluteMpegFrames) {
const int sampleRate = props->sampleRate();
if (sampleRate != 0) {
hasLyrics = true;
free(val);
for (const auto &line: frame->synchedText()) {
const uint timeInMs = (line.time * 1000) / sampleRate;
char *text = (char *)line.text.toCString(true);
go_map_put_lyric_line(id, lang, text, timeInMs);
}
}
if (hasLyrics) {
go_map_put_int(id, CUSTOM_LYRICS, 1);
}
} else {
tags.insert(kv.first, kv.second.front()->toString());
}
}
}
if (hasLyrics) {
go_map_put_int(id, CUSTOM_LYRICS, 1);
}
}
// M4A may have some iTunes specific tags
TagLib::MP4::File *m4afile(dynamic_cast<TagLib::MP4::File *>(f.file()));
if (m4afile != NULL)
{
const auto itemListMap = m4afile->tag();
{
char buf[200];
for (const char *key : RG_TAGS)
{
snprintf(buf, sizeof(buf), "----:com.apple.iTunes:%s", key);
const auto item = itemListMap->item(buf);
if (item.isValid())
{
char *dup = ::strdup(key);
char *val = ::strdup(item.toStringList().front().toCString(true));
go_map_put_str(id, dup, val);
free(dup);
free(val);
}
if (m4afile != NULL) {
const auto itemListMap = m4afile->tag()->itemMap();
for (const auto item: itemListMap) {
char *key = (char *)item.first.toCString(true);
for (const auto value: item.second.toStringList()) {
char *val = (char *)value.toCString(true);
go_map_put_m4a_str(id, key, val);
}
}
}
// WMA/ASF files may have additional tags not captured by the general iterator
TagLib::ASF::File *asfFile(dynamic_cast<TagLib::ASF::File *>(f.file()));
if (asfFile != NULL)
{
if (asfFile != NULL) {
const TagLib::ASF::Tag *asfTags{asfFile->tag()};
const auto itemListMap = asfTags->attributeListMap();
for (const auto item : itemListMap) {
char *key = ::strdup(item.first.toCString(true));
for (const auto &value: item.second) {
char *val = ::strdup(value.toString().toCString());
go_map_put_str(id, key, val);
free(val);
}
free(key);
}
// Compilation tag needs to be handled differently
const auto compilation = asfTags->attribute("WM/IsCompilation");
if (!compilation.isEmpty()) {
char *val = ::strdup(compilation.front().toString().toCString());
go_map_put_str(id, (char *)"compilation", val);
free(val);
tags.insert(item.first, item.second.front().toString());
}
}
if (has_cover(f)) {
go_map_put_str(id, (char *)"has_picture", (char *)"true");
}
// Send all collected tags to the Go map
for (TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end();
++i) {
char *key = (char *)i->first.toCString(true);
for (TagLib::StringList::ConstIterator j = i->second.begin();
j != i->second.end(); ++j) {
char *key = ::strdup(i->first.toCString(true));
char *val = ::strdup((*j).toCString(true));
char *val = (char *)(*j).toCString(true);
go_map_put_str(id, key, val);
free(key);
free(val);
}
}
// Cover art has to be handled separately
if (has_cover(f)) {
go_map_put_str(id, (char *)"has_picture", (char *)"true");
}
return 0;
}
// Detect if the file has cover art. Returns 1 if the file has cover art, 0 otherwise.
char has_cover(const TagLib::FileRef f) {
char hasCover = 0;
// ----- MP3

View file

@ -21,8 +21,11 @@ import (
"unsafe"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/scanner/metadata"
)
const iTunesKeyPrefix = "----:com.apple.itunes:"
func Read(filename string) (tags map[string][]string, err error) {
// Do not crash on failures in the C code/library
debug.SetPanicOnFault(true)
@ -79,14 +82,31 @@ func deleteMap(id uint32) {
delete(maps, id)
}
//export go_map_put_m4a_str
func go_map_put_m4a_str(id C.ulong, key *C.char, val *C.char) {
k := strings.ToLower(C.GoString(key))
// Special for M4A, do not catch keys that have no actual name
k = strings.TrimPrefix(k, iTunesKeyPrefix)
do_put_map(id, k, val)
}
//export go_map_put_str
func go_map_put_str(id C.ulong, key *C.char, val *C.char) {
k := strings.ToLower(C.GoString(key))
do_put_map(id, k, val)
}
func do_put_map(id C.ulong, key string, val *C.char) {
if key == "" {
return
}
lock.RLock()
defer lock.RUnlock()
m := maps[uint32(id)]
k := strings.ToLower(C.GoString(key))
v := strings.TrimSpace(C.GoString(val))
m[k] = append(m[k], v)
m[key] = append(m[key], v)
}
//export go_map_put_int
@ -96,3 +116,34 @@ 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.uint) {
language := C.GoString(lang)
line := C.GoString(text)
timeGo := uint64(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()
m := maps[uint32(id)]
existing, ok := m[metadata.NAVIDROME_SYNCHRONIZED_KEY]
if ok {
length := len(existing)
if existing[length-2] == language {
existing[length-1] += formatted_line
} else {
m[metadata.NAVIDROME_SYNCHRONIZED_KEY] = append(existing, language, formatted_line)
}
} else {
m[metadata.NAVIDROME_SYNCHRONIZED_KEY] = append(existing, language, formatted_line)
}
}

View file

@ -11,8 +11,10 @@ extern "C" {
#define FILENAME_CHAR_T char
#endif
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_lyric_line(unsigned long id, char *lang, char *text, uint time);
int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id);
#ifdef __cplusplus

View file

@ -10,9 +10,10 @@ var _ = Describe("TagScanner", func() {
It("return all audio files from the folder", func() {
files, err := loadAllAudioFiles("tests/fixtures")
Expect(err).ToNot(HaveOccurred())
Expect(files).To(HaveLen(10))
Expect(files).To(HaveLen(11))
Expect(files).To(HaveKey("tests/fixtures/test.aiff"))
Expect(files).To(HaveKey("tests/fixtures/test.flac"))
Expect(files).To(HaveKey("tests/fixtures/test.m4a"))
Expect(files).To(HaveKey("tests/fixtures/test.mp3"))
Expect(files).To(HaveKey("tests/fixtures/test.ogg"))
Expect(files).To(HaveKey("tests/fixtures/test.wav"))

View file

@ -34,7 +34,7 @@ var _ = Describe("walk_dir_tree", func() {
Expect(collected[baseDir]).To(MatchFields(IgnoreExtras, Fields{
"Images": BeEmpty(),
"HasPlaylist": BeFalse(),
"AudioFilesCount": BeNumerically("==", 11),
"AudioFilesCount": BeNumerically("==", 12),
}))
Expect(collected[filepath.Join(baseDir, "artist", "an-album")]).To(MatchFields(IgnoreExtras, Fields{
"Images": ConsistOf("cover.jpg", "front.png", "artist.png"),

View file

@ -23,7 +23,7 @@ func (p *Router) handleStream(w http.ResponseWriter, r *http.Request) {
return
}
stream, err := p.streamer.NewStream(ctx, info.id, info.format, info.bitrate)
stream, err := p.streamer.NewStream(ctx, info.id, info.format, info.bitrate, 0)
if err != nil {
log.Error(ctx, "Error starting shared stream", err)
http.Error(w, "invalid request", http.StatusInternalServerError)

View file

@ -432,6 +432,8 @@ func (api *Router) buildAlbum(ctx context.Context, album *model.Album, mfs model
dir.Starred = &album.StarredAt
}
dir.MusicBrainzId = album.MbzAlbumID
dir.IsCompilation = album.Compilation
dir.SortName = album.SortAlbumName
dir.Song = childrenFromMediaFiles(ctx, mfs)
return dir
}

View file

@ -111,6 +111,7 @@ func toArtistID3(r *http.Request, a model.Artist) responses.ArtistID3 {
ArtistImageUrl: public.ImageURL(r, a.CoverArtID(), 600),
UserRating: int32(a.Rating),
MusicBrainzId: a.MbzArtistID,
SortName: a.SortArtistName,
}
if a.Starred {
artist.Starred = &a.StarredAt
@ -187,7 +188,16 @@ func childFromMediaFile(ctx context.Context, mf model.MediaFile) responses.Child
}
child.BookmarkPosition = mf.BookmarkPosition
child.Comment = mf.Comment
child.SortName = mf.SortTitle
child.Bpm = int32(mf.Bpm)
child.MediaType = responses.MediaTypeSong
child.MusicBrainzId = mf.MbzRecordingID
child.ReplayGain = responses.ReplayGain{
TrackGain: mf.RGTrackGain,
AlbumGain: mf.RGAlbumGain,
TrackPeak: mf.RGTrackPeak,
AlbumPeak: mf.RGAlbumPeak,
}
return child
}
@ -236,6 +246,9 @@ func childFromAlbum(_ context.Context, al model.Album) responses.Child {
child.Played = &al.PlayDate
}
child.UserRating = int32(al.Rating)
child.SortName = al.SortAlbumName
child.MediaType = responses.MediaTypeAlbum
child.MusicBrainzId = al.MbzAlbumID
return child
}

View file

@ -9,14 +9,9 @@ import (
func (api *Router) GetOpenSubsonicExtensions(_ *http.Request) (*responses.Subsonic, error) {
response := newResponse()
response.OpenSubsonicExtensions = &responses.OpenSubsonicExtensions{
OpenSubsonicExtensions: []responses.OpenSubsonicExtension{
{
Name: "songLyrics",
Versions: []responses.Version{{
Version: 1,
}},
},
},
{Name: "transcodeOffset", Versions: []int32{1}},
{Name: "formPost", Versions: []int32{1}},
{Name: "songLyrics", Versions: []int32{1}},
}
return response, nil
}

View file

@ -10,10 +10,14 @@
"id": "1",
"isDir": false,
"title": "title",
"genres": [],
"isVideo": false,
"bpm": 0,
"comment": ""
"comment": "",
"sortName": "",
"mediaType": "",
"musicBrainzId": "",
"genres": [],
"replayGain": {}
}
]
}

View file

@ -1,5 +1,7 @@
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0" openSubsonic="true">
<albumList>
<album id="1" isDir="false" title="title" isVideo="false" bpm="0" comment=""></album>
<album id="1" isDir="false" title="title" isVideo="false" bpm="0" comment="" sortName="" mediaType="" musicBrainzId="">
<replayGain></replayGain>
</album>
</albumList>
</subsonic-response>

View file

@ -8,8 +8,8 @@
"id": "1",
"name": "album",
"artist": "artist",
"userRating": 0,
"genre": "rock",
"userRating": 0,
"genres": [
{
"name": "rock"
@ -19,6 +19,8 @@
}
],
"musicBrainzId": "1234",
"isCompilation": true,
"sortName": "sorted album",
"song": [
{
"id": "1",
@ -29,14 +31,6 @@
"track": 1,
"year": 1985,
"genre": "Rock",
"genres": [
{
"name": "rock"
},
{
"name": "progressive"
}
],
"coverArt": "1",
"size": 8421341,
"contentType": "audio/flac",
@ -48,7 +42,26 @@
"bitRate": 320,
"isVideo": false,
"bpm": 127,
"comment": "a comment"
"comment": "a comment",
"sortName": "sorted song",
"mediaType": "song",
"musicBrainzId": "4321",
"genres": [
{
"name": "rock"
},
{
"name": "progressive"
}
],
"replayGain": {
"trackGain": 1,
"albumGain": 2,
"trackPeak": 3,
"albumPeak": 4,
"baseGain": 5,
"fallbackGain": 6
}
}
]
}

Some files were not shown because too many files have changed in this diff Show more