mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-28 06:07:17 +01:00
sylt, move some things to wrapper
This commit is contained in:
commit
c1d4fa8780
136 changed files with 985 additions and 566 deletions
2
Makefile
2
Makefile
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 -",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
7
go.mod
|
|
@ -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
40
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@
|
|||
}
|
||||
},
|
||||
"share": {
|
||||
"name": "Compartición ||| Comparticións",
|
||||
"name": "Compartido |||| Compartidos",
|
||||
"fields": {
|
||||
"username": "Compartida por",
|
||||
"url": "URL",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,14 @@
|
|||
"id": "1",
|
||||
"isDir": false,
|
||||
"title": "title",
|
||||
"genres": [],
|
||||
"isVideo": false,
|
||||
"bpm": 0,
|
||||
"comment": ""
|
||||
"comment": "",
|
||||
"sortName": "",
|
||||
"mediaType": "",
|
||||
"musicBrainzId": "",
|
||||
"genres": [],
|
||||
"replayGain": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue