Add inital multiple artist support
This commit is contained in:
@@ -34,6 +34,7 @@ func main() {
|
|||||||
confScanInterval := set.Int("scan-interval", 0, "interval (in minutes) to automatically scan music (optional)")
|
confScanInterval := set.Int("scan-interval", 0, "interval (in minutes) to automatically scan music (optional)")
|
||||||
confJukeboxEnabled := set.Bool("jukebox-enabled", false, "whether the subsonic jukebox api should be enabled (optional)")
|
confJukeboxEnabled := set.Bool("jukebox-enabled", false, "whether the subsonic jukebox api should be enabled (optional)")
|
||||||
confProxyPrefix := set.String("proxy-prefix", "", "url path prefix to use if behind proxy. eg '/gonic' (optional)")
|
confProxyPrefix := set.String("proxy-prefix", "", "url path prefix to use if behind proxy. eg '/gonic' (optional)")
|
||||||
|
confGenreSplit := set.String("genre-split", "\n", "character or string to split genre tag data on (optional)")
|
||||||
confShowVersion := set.Bool("version", false, "show gonic version")
|
confShowVersion := set.Bool("version", false, "show gonic version")
|
||||||
_ = set.String("config-path", "", "path to config (optional)")
|
_ = set.String("config-path", "", "path to config (optional)")
|
||||||
|
|
||||||
@@ -85,6 +86,7 @@ func main() {
|
|||||||
CachePath: *confCachePath,
|
CachePath: *confCachePath,
|
||||||
CoverCachePath: coverCachePath,
|
CoverCachePath: coverCachePath,
|
||||||
ProxyPrefix: *confProxyPrefix,
|
ProxyPrefix: *confProxyPrefix,
|
||||||
|
GenreSplit: *confGenreSplit,
|
||||||
})
|
})
|
||||||
|
|
||||||
var g run.Group
|
var g run.Group
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func (c *Controller) ServeGetAlbum(r *http.Request) *spec.Response {
|
|||||||
Select("albums.*, count(tracks.id) child_count, sum(tracks.length) duration").
|
Select("albums.*, count(tracks.id) child_count, sum(tracks.length) duration").
|
||||||
Joins("LEFT JOIN tracks ON tracks.album_id=albums.id").
|
Joins("LEFT JOIN tracks ON tracks.album_id=albums.id").
|
||||||
Preload("TagArtist").
|
Preload("TagArtist").
|
||||||
Preload("TagGenre").
|
Preload("Genres").
|
||||||
Preload("Tracks", func(db *gorm.DB) *gorm.DB {
|
Preload("Tracks", func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Order("tracks.tag_disc_number, tracks.tag_track_number")
|
return db.Order("tracks.tag_disc_number, tracks.tag_track_number")
|
||||||
}).
|
}).
|
||||||
@@ -123,8 +123,9 @@ func (c *Controller) ServeGetAlbumListTwo(r *http.Request) *spec.Response {
|
|||||||
params.GetOrInt("toYear", 2200))
|
params.GetOrInt("toYear", 2200))
|
||||||
q = q.Order("tag_year")
|
q = q.Order("tag_year")
|
||||||
case "byGenre":
|
case "byGenre":
|
||||||
q = q.Joins("JOIN genres ON albums.tag_genre_id=genres.id AND genres.name=?",
|
genre, _ := params.Get("genre")
|
||||||
params.GetOr("genre", "Unknown Genre"))
|
q = q.Joins("JOIN album_genres ON album_genres.album_id=albums.id")
|
||||||
|
q = q.Joins("JOIN genres ON genres.id=album_genres.genre_id AND genres.name=?", genre)
|
||||||
case "frequent":
|
case "frequent":
|
||||||
user := r.Context().Value(CtxUser).(*db.User)
|
user := r.Context().Value(CtxUser).(*db.User)
|
||||||
q = q.Joins("JOIN plays ON albums.id=plays.album_id AND plays.user_id=?",
|
q = q.Joins("JOIN plays ON albums.id=plays.album_id AND plays.user_id=?",
|
||||||
@@ -291,8 +292,8 @@ func (c *Controller) ServeGetGenres(r *http.Request) *spec.Response {
|
|||||||
var genres []*db.Genre
|
var genres []*db.Genre
|
||||||
c.DB.
|
c.DB.
|
||||||
Select(`*,
|
Select(`*,
|
||||||
(SELECT count(id) FROM albums WHERE tag_genre_id=genres.id) album_count,
|
(SELECT count(1) FROM album_genres WHERE genre_id=genres.id) album_count,
|
||||||
(SELECT count(id) FROM tracks WHERE tag_genre_id=genres.id) track_count`).
|
(SELECT count(1) FROM track_genres WHERE genre_id=genres.id) track_count`).
|
||||||
Group("genres.id").
|
Group("genres.id").
|
||||||
Find(&genres)
|
Find(&genres)
|
||||||
sub := spec.NewResponse()
|
sub := spec.NewResponse()
|
||||||
@@ -316,7 +317,8 @@ func (c *Controller) ServeGetSongsByGenre(r *http.Request) *spec.Response {
|
|||||||
var tracks []*db.Track
|
var tracks []*db.Track
|
||||||
c.DB.
|
c.DB.
|
||||||
Joins("JOIN albums ON tracks.album_id=albums.id").
|
Joins("JOIN albums ON tracks.album_id=albums.id").
|
||||||
Joins("JOIN genres ON tracks.tag_genre_id=genres.id AND genres.name=?", genre).
|
Joins("JOIN track_genres ON track_genres.track_id=tracks.id").
|
||||||
|
Joins("JOIN genres ON track_genres.genre_id=genres.id AND genres.name=?", genre).
|
||||||
Preload("Album").
|
Preload("Album").
|
||||||
Offset(params.GetOrInt("offset", 0)).
|
Offset(params.GetOrInt("offset", 0)).
|
||||||
Limit(params.GetOrInt("count", 10)).
|
Limit(params.GetOrInt("count", 10)).
|
||||||
|
|||||||
@@ -197,9 +197,9 @@ func (c *Controller) ServeGetRandomSongs(r *http.Request) *spec.Response {
|
|||||||
params := r.Context().Value(CtxParams).(params.Params)
|
params := r.Context().Value(CtxParams).(params.Params)
|
||||||
var tracks []*db.Track
|
var tracks []*db.Track
|
||||||
q := c.DB.DB.
|
q := c.DB.DB.
|
||||||
Joins("JOIN albums ON tracks.album_id=albums.id").
|
|
||||||
Limit(params.GetOrInt("size", 10)).
|
Limit(params.GetOrInt("size", 10)).
|
||||||
Preload("Album").
|
Preload("Album").
|
||||||
|
Joins("JOIN albums ON tracks.album_id=albums.id").
|
||||||
Order(gorm.Expr("random()"))
|
Order(gorm.Expr("random()"))
|
||||||
if year, err := params.GetInt("fromYear"); err == nil {
|
if year, err := params.GetInt("fromYear"); err == nil {
|
||||||
q = q.Where("albums.tag_year >= ?", year)
|
q = q.Where("albums.tag_year >= ?", year)
|
||||||
@@ -208,10 +208,8 @@ func (c *Controller) ServeGetRandomSongs(r *http.Request) *spec.Response {
|
|||||||
q = q.Where("albums.tag_year <= ?", year)
|
q = q.Where("albums.tag_year <= ?", year)
|
||||||
}
|
}
|
||||||
if genre, err := params.Get("genre"); err == nil {
|
if genre, err := params.Get("genre"); err == nil {
|
||||||
q = q.Joins(
|
q = q.Joins("JOIN track_genres ON track_genres.track_id=tracks.id")
|
||||||
"JOIN genres ON tracks.tag_genre_id=genres.id AND genres.name=?",
|
q = q.Joins("JOIN genres ON genres.id=track_genres.genre_id AND genres.name=?", genre)
|
||||||
genre,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
q.Find(&tracks)
|
q.Find(&tracks)
|
||||||
sub := spec.NewResponse()
|
sub := spec.NewResponse()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package spec
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"go.senan.xyz/gonic/server/db"
|
"go.senan.xyz/gonic/server/db"
|
||||||
)
|
)
|
||||||
@@ -13,11 +14,9 @@ func NewAlbumByTags(a *db.Album, artist *db.Artist) *Album {
|
|||||||
Name: a.TagTitle,
|
Name: a.TagTitle,
|
||||||
Year: a.TagYear,
|
Year: a.TagYear,
|
||||||
TrackCount: a.ChildCount,
|
TrackCount: a.ChildCount,
|
||||||
|
Genre: strings.Join(a.GenreStrings(), ", "),
|
||||||
Duration: a.Duration,
|
Duration: a.Duration,
|
||||||
}
|
}
|
||||||
if a.TagGenre != nil {
|
|
||||||
ret.Genre = a.TagGenre.Name
|
|
||||||
}
|
|
||||||
if a.Cover != "" {
|
if a.Cover != "" {
|
||||||
ret.CoverID = a.SID()
|
ret.CoverID = a.SID()
|
||||||
}
|
}
|
||||||
@@ -47,6 +46,7 @@ func NewTrackByTags(t *db.Track, album *db.Album) *TrackChild {
|
|||||||
),
|
),
|
||||||
Album: album.TagTitle,
|
Album: album.TagTitle,
|
||||||
AlbumID: album.SID(),
|
AlbumID: album.SID(),
|
||||||
|
Genre: strings.Join(t.GenreStrings(), ", "),
|
||||||
Duration: t.Length,
|
Duration: t.Length,
|
||||||
Bitrate: t.Bitrate,
|
Bitrate: t.Bitrate,
|
||||||
Type: "music",
|
Type: "music",
|
||||||
|
|||||||
@@ -42,12 +42,16 @@ func New(in string) (ID, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ID{}, fmt.Errorf("%q: %w", partValue, ErrNotAnInt)
|
return ID{}, fmt.Errorf("%q: %w", partValue, ErrNotAnInt)
|
||||||
}
|
}
|
||||||
for _, acc := range []IDT{Artist, Album, Track} {
|
switch IDT(partType) {
|
||||||
if partType == string(acc) {
|
case Artist:
|
||||||
return ID{Type: acc, Value: val}, nil
|
return ID{Type: Artist, Value: val}, nil
|
||||||
}
|
case Album:
|
||||||
|
return ID{Type: Album, Value: val}, nil
|
||||||
|
case Track:
|
||||||
|
return ID{Type: Track, Value: val}, nil
|
||||||
|
default:
|
||||||
|
return ID{}, fmt.Errorf("%q: %w", partType, ErrBadPrefix)
|
||||||
}
|
}
|
||||||
return ID{}, fmt.Errorf("%q: %w", partType, ErrBadPrefix)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i ID) String() string {
|
func (i ID) String() string {
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ func New(path string) (*DB, error) {
|
|||||||
migrateAddGenre(),
|
migrateAddGenre(),
|
||||||
migrateUpdateTranscodePrefIDX(),
|
migrateUpdateTranscodePrefIDX(),
|
||||||
migrateAddAlbumIDX(),
|
migrateAddAlbumIDX(),
|
||||||
|
migrateMultiGenre(),
|
||||||
))
|
))
|
||||||
if err = migr.Migrate(); err != nil {
|
if err = migr.Migrate(); err != nil {
|
||||||
return nil, fmt.Errorf("migrating to latest version: %w", err)
|
return nil, fmt.Errorf("migrating to latest version: %w", err)
|
||||||
|
|||||||
@@ -61,14 +61,14 @@ func migrateMergePlaylist() gormigrate.Migration {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return tx.Exec(`
|
return tx.Exec(`
|
||||||
UPDATE playlists
|
UPDATE playlists
|
||||||
SET items=( SELECT group_concat(track_id) FROM (
|
SET items=( SELECT group_concat(track_id) FROM (
|
||||||
SELECT track_id
|
SELECT track_id
|
||||||
FROM playlist_items
|
FROM playlist_items
|
||||||
WHERE playlist_items.playlist_id=playlists.id
|
WHERE playlist_items.playlist_id=playlists.id
|
||||||
ORDER BY created_at
|
ORDER BY created_at
|
||||||
) );
|
) );
|
||||||
DROP TABLE playlist_items;`,
|
DROP TABLE playlist_items;`,
|
||||||
).
|
).
|
||||||
Error
|
Error
|
||||||
},
|
},
|
||||||
@@ -117,8 +117,8 @@ func migrateUpdateTranscodePrefIDX() gormigrate.Migration {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
step := tx.Exec(`
|
step := tx.Exec(`
|
||||||
ALTER TABLE transcode_preferences RENAME TO transcode_preferences_orig;
|
ALTER TABLE transcode_preferences RENAME TO transcode_preferences_orig;
|
||||||
`)
|
`)
|
||||||
if err := step.Error; err != nil {
|
if err := step.Error; err != nil {
|
||||||
return fmt.Errorf("step rename: %w", err)
|
return fmt.Errorf("step rename: %w", err)
|
||||||
}
|
}
|
||||||
@@ -129,11 +129,11 @@ func migrateUpdateTranscodePrefIDX() gormigrate.Migration {
|
|||||||
return fmt.Errorf("step create: %w", err)
|
return fmt.Errorf("step create: %w", err)
|
||||||
}
|
}
|
||||||
step = tx.Exec(`
|
step = tx.Exec(`
|
||||||
INSERT INTO transcode_preferences (user_id, client, profile)
|
INSERT INTO transcode_preferences (user_id, client, profile)
|
||||||
SELECT user_id, client, profile
|
SELECT user_id, client, profile
|
||||||
FROM transcode_preferences_orig;
|
FROM transcode_preferences_orig;
|
||||||
DROP TABLE transcode_preferences_orig;
|
DROP TABLE transcode_preferences_orig;
|
||||||
`)
|
`)
|
||||||
if err := step.Error; err != nil {
|
if err := step.Error; err != nil {
|
||||||
return fmt.Errorf("step copy: %w", err)
|
return fmt.Errorf("step copy: %w", err)
|
||||||
}
|
}
|
||||||
@@ -153,3 +153,19 @@ func migrateAddAlbumIDX() gormigrate.Migration {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migrateMultiGenre() gormigrate.Migration {
|
||||||
|
return gormigrate.Migration{
|
||||||
|
ID: "202012151806",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
return tx.AutoMigrate(
|
||||||
|
Track{},
|
||||||
|
Album{},
|
||||||
|
Genre{},
|
||||||
|
TrackGenre{},
|
||||||
|
AlbumGenre{},
|
||||||
|
).
|
||||||
|
Error
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,12 +61,10 @@ func (a *Artist) IndexName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Genre struct {
|
type Genre struct {
|
||||||
ID int `gorm:"primary_key"`
|
ID int `gorm:"primary_key"`
|
||||||
Name string `gorm:"not null; unique_index"`
|
Name string `gorm:"not null; unique_index"`
|
||||||
Albums []*Album `gorm:"foreignkey:TagGenreID"`
|
AlbumCount int `sql:"-"`
|
||||||
AlbumCount int `sql:"-"`
|
TrackCount int `sql:"-"`
|
||||||
Tracks []*Track `gorm:"foreignkey:TagGenreID"`
|
|
||||||
TrackCount int `sql:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Track struct {
|
type Track struct {
|
||||||
@@ -78,18 +76,17 @@ type Track struct {
|
|||||||
Album *Album
|
Album *Album
|
||||||
AlbumID int `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
|
AlbumID int `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
|
||||||
Artist *Artist
|
Artist *Artist
|
||||||
ArtistID int `gorm:"not null" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
|
ArtistID int `gorm:"not null" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
|
||||||
Size int `gorm:"not null" sql:"default: null"`
|
Genres []*Genre `gorm:"many2many:track_genres"`
|
||||||
Length int `sql:"default: null"`
|
Size int `gorm:"not null" sql:"default: null"`
|
||||||
Bitrate int `sql:"default: null"`
|
Length int `sql:"default: null"`
|
||||||
TagTitle string `sql:"default: null"`
|
Bitrate int `sql:"default: null"`
|
||||||
TagTitleUDec string `sql:"default: null"`
|
TagTitle string `sql:"default: null"`
|
||||||
TagTrackArtist string `sql:"default: null"`
|
TagTitleUDec string `sql:"default: null"`
|
||||||
TagTrackNumber int `sql:"default: null"`
|
TagTrackArtist string `sql:"default: null"`
|
||||||
TagDiscNumber int `sql:"default: null"`
|
TagTrackNumber int `sql:"default: null"`
|
||||||
TagGenre *Genre
|
TagDiscNumber int `sql:"default: null"`
|
||||||
TagGenreID int `sql:"default: null; type:int REFERENCES genres(id)"`
|
TagBrainzID string `sql:"default: null"`
|
||||||
TagBrainzID string `sql:"default: null"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Track) SID() *specid.ID {
|
func (t *Track) SID() *specid.ID {
|
||||||
@@ -128,6 +125,14 @@ func (t *Track) RelPath() string {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Track) GenreStrings() []string {
|
||||||
|
var strs []string
|
||||||
|
for _, genre := range t.Genres {
|
||||||
|
strs = append(strs, genre.Name)
|
||||||
|
}
|
||||||
|
return strs
|
||||||
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int `gorm:"primary_key"`
|
ID int `gorm:"primary_key"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
@@ -160,12 +165,11 @@ type Album struct {
|
|||||||
RightPath string `gorm:"not null; unique_index:idx_left_path_right_path" sql:"default: null"`
|
RightPath string `gorm:"not null; unique_index:idx_left_path_right_path" sql:"default: null"`
|
||||||
RightPathUDec string `sql:"default: null"`
|
RightPathUDec string `sql:"default: null"`
|
||||||
Parent *Album
|
Parent *Album
|
||||||
ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
|
ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
|
||||||
Cover string `sql:"default: null"`
|
Genres []*Genre `gorm:"many2many:album_genres"`
|
||||||
|
Cover string `sql:"default: null"`
|
||||||
TagArtist *Artist
|
TagArtist *Artist
|
||||||
TagArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
|
TagArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
|
||||||
TagGenre *Genre
|
|
||||||
TagGenreID int `sql:"default: null; type:int"`
|
|
||||||
TagTitle string `sql:"default: null"`
|
TagTitle string `sql:"default: null"`
|
||||||
TagTitleUDec string `sql:"default: null"`
|
TagTitleUDec string `sql:"default: null"`
|
||||||
TagBrainzID string `sql:"default: null"`
|
TagBrainzID string `sql:"default: null"`
|
||||||
@@ -192,6 +196,14 @@ func (a *Album) IndexRightPath() string {
|
|||||||
return a.RightPath
|
return a.RightPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Album) GenreStrings() []string {
|
||||||
|
var strs []string
|
||||||
|
for _, genre := range a.Genres {
|
||||||
|
strs = append(strs, genre.Name)
|
||||||
|
}
|
||||||
|
return strs
|
||||||
|
}
|
||||||
|
|
||||||
type Playlist struct {
|
type Playlist struct {
|
||||||
ID int `gorm:"primary_key"`
|
ID int `gorm:"primary_key"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
@@ -243,3 +255,17 @@ type TranscodePreference struct {
|
|||||||
Client string `gorm:"not null; unique_index:idx_user_id_client" sql:"default: null"`
|
Client string `gorm:"not null; unique_index:idx_user_id_client" sql:"default: null"`
|
||||||
Profile string `gorm:"not null" sql:"default: null"`
|
Profile string `gorm:"not null" sql:"default: null"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TrackGenre struct {
|
||||||
|
Track *Track
|
||||||
|
TrackID int `gorm:"not null; unique_index:idx_track_id_genre_id" sql:"default: null; type:int REFERENCES tracks(id) ON DELETE CASCADE"`
|
||||||
|
Genre *Genre
|
||||||
|
GenreID int `gorm:"not null; unique_index:idx_track_id_genre_id" sql:"default: null; type:int REFERENCES genres(id) ON DELETE CASCADE"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlbumGenre struct {
|
||||||
|
Album *Album
|
||||||
|
AlbumID int `gorm:"not null; unique_index:idx_album_id_genre_id" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
|
||||||
|
Genre *Genre
|
||||||
|
GenreID int `gorm:"not null; unique_index:idx_album_id_genre_id" sql:"default: null; type:int REFERENCES genres(id) ON DELETE CASCADE"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,9 +58,10 @@ func SetScanning() func() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Scanner struct {
|
type Scanner struct {
|
||||||
db *db.DB
|
db *db.DB
|
||||||
musicPath string
|
musicPath string
|
||||||
isFull bool
|
isFull bool
|
||||||
|
genreSplit string
|
||||||
// these two are for the transaction we do for every folder.
|
// these two are for the transaction we do for every folder.
|
||||||
// the boolean is there so we dont begin or commit multiple
|
// the boolean is there so we dont begin or commit multiple
|
||||||
// times in the handle folder or post children callback
|
// times in the handle folder or post children callback
|
||||||
@@ -78,10 +79,11 @@ type Scanner struct {
|
|||||||
seenTracksNew int // n tracks not seen before
|
seenTracksNew int // n tracks not seen before
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(musicPath string, db *db.DB) *Scanner {
|
func New(musicPath string, db *db.DB, genreSplit string) *Scanner {
|
||||||
return &Scanner{
|
return &Scanner{
|
||||||
db: db,
|
db: db,
|
||||||
musicPath: musicPath,
|
musicPath: musicPath,
|
||||||
|
genreSplit: genreSplit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,6 +370,7 @@ func (s *Scanner) handleTrack(it *item) error {
|
|||||||
s.trTx = s.db.Begin()
|
s.trTx = s.db.Begin()
|
||||||
s.trTxOpen = true
|
s.trTxOpen = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ** begin set track basics
|
// ** begin set track basics
|
||||||
track := &db.Track{}
|
track := &db.Track{}
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -404,16 +407,9 @@ func (s *Scanner) handleTrack(it *item) error {
|
|||||||
track.TagBrainzID = trTags.BrainzID()
|
track.TagBrainzID = trTags.BrainzID()
|
||||||
track.Length = trTags.Length() // these two should be calculated
|
track.Length = trTags.Length() // these two should be calculated
|
||||||
track.Bitrate = trTags.Bitrate() // ...from the file instead of tags
|
track.Bitrate = trTags.Bitrate() // ...from the file instead of tags
|
||||||
|
|
||||||
// ** begin set album artist basics
|
// ** begin set album artist basics
|
||||||
artistName := func() string {
|
artistName := firstTag("Unknown Artist", trTags.AlbumArtist, trTags.Artist)
|
||||||
if r := trTags.AlbumArtist(); r != "" {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
if r := trTags.Artist(); r != "" {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
return "Unknown Artist"
|
|
||||||
}()
|
|
||||||
artist := &db.Artist{}
|
artist := &db.Artist{}
|
||||||
err = s.trTx.
|
err = s.trTx.
|
||||||
Select("id").
|
Select("id").
|
||||||
@@ -428,43 +424,66 @@ func (s *Scanner) handleTrack(it *item) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
track.ArtistID = artist.ID
|
track.ArtistID = artist.ID
|
||||||
|
|
||||||
// ** begin set genre
|
// ** begin set genre
|
||||||
genreName := func() string {
|
genreTag := firstTag("Unknown Genre", trTags.Genre)
|
||||||
if r := trTags.Genre(); r != "" {
|
genres := strings.Split(genreTag, s.genreSplit)
|
||||||
return r
|
genreIDs := []int{}
|
||||||
}
|
for _, genreName := range genres {
|
||||||
return "Unknown Genre"
|
// TODO insert or ignore
|
||||||
}()
|
genre := &db.Genre{}
|
||||||
genre := &db.Genre{}
|
err = s.trTx.
|
||||||
err = s.trTx.
|
Select("id").
|
||||||
Select("id").
|
Where("name=?", genreName).
|
||||||
Where("name=?", genreName).
|
First(genre).
|
||||||
First(genre).
|
Error
|
||||||
Error
|
if gorm.IsRecordNotFoundError(err) {
|
||||||
if gorm.IsRecordNotFoundError(err) {
|
genre.Name = genreName
|
||||||
genre.Name = genreName
|
if err := s.trTx.Save(genre).Error; err != nil {
|
||||||
if err := s.trTx.Save(genre).Error; err != nil {
|
return fmt.Errorf("writing genres table: %w", err)
|
||||||
return fmt.Errorf("writing genres table: %w", err)
|
}
|
||||||
}
|
}
|
||||||
|
genreIDs = append(genreIDs, genre.ID)
|
||||||
}
|
}
|
||||||
track.TagGenreID = genre.ID
|
|
||||||
// ** begin save the track
|
// ** begin save the track
|
||||||
if err := s.trTx.Save(track).Error; err != nil {
|
if err := s.trTx.Save(track).Error; err != nil {
|
||||||
return fmt.Errorf("writing track table: %w", err)
|
return fmt.Errorf("writing track table: %w", err)
|
||||||
}
|
}
|
||||||
|
for _, genreID := range genreIDs {
|
||||||
|
trackGenre := &db.TrackGenre{TrackID: track.ID, GenreID: genreID}
|
||||||
|
if err := s.trTx.Save(trackGenre).Error; err != nil {
|
||||||
|
return fmt.Errorf("writing track table: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
s.seenTracksNew++
|
s.seenTracksNew++
|
||||||
|
|
||||||
// ** begin set album if this is the first track in the folder
|
// ** begin set album if this is the first track in the folder
|
||||||
folder := s.curFolders.Peek()
|
folder := s.curFolders.Peek()
|
||||||
if !folder.ReceivedPaths || folder.ReceivedTags {
|
if !folder.ReceivedPaths || folder.ReceivedTags {
|
||||||
// the folder hasn't been modified or already has it's tags
|
// the folder hasn't been modified or already has it's tags
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
for _, genreID := range genreIDs {
|
||||||
|
albumGenre := &db.AlbumGenre{AlbumID: folder.ID, GenreID: genreID}
|
||||||
|
if err := s.trTx.Save(albumGenre).Error; err != nil {
|
||||||
|
return fmt.Errorf("writing album table: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
folder.TagTitle = trTags.Album()
|
folder.TagTitle = trTags.Album()
|
||||||
folder.TagTitleUDec = decoded(trTags.Album())
|
folder.TagTitleUDec = decoded(trTags.Album())
|
||||||
folder.TagBrainzID = trTags.AlbumBrainzID()
|
folder.TagBrainzID = trTags.AlbumBrainzID()
|
||||||
folder.TagYear = trTags.Year()
|
folder.TagYear = trTags.Year()
|
||||||
folder.TagArtistID = artist.ID
|
folder.TagArtistID = artist.ID
|
||||||
folder.TagGenreID = genre.ID
|
|
||||||
folder.ReceivedTags = true
|
folder.ReceivedTags = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func firstTag(fallback string, tags ...func() string) string {
|
||||||
|
for _, f := range tags {
|
||||||
|
if tag := f(); tag != "" {
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ type Options struct {
|
|||||||
CachePath string
|
CachePath string
|
||||||
CoverCachePath string
|
CoverCachePath string
|
||||||
ProxyPrefix string
|
ProxyPrefix string
|
||||||
|
GenreSplit string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -40,7 +41,7 @@ func New(opts Options) *Server {
|
|||||||
opts.MusicPath = filepath.Clean(opts.MusicPath)
|
opts.MusicPath = filepath.Clean(opts.MusicPath)
|
||||||
opts.CachePath = filepath.Clean(opts.CachePath)
|
opts.CachePath = filepath.Clean(opts.CachePath)
|
||||||
// ** begin controllers
|
// ** begin controllers
|
||||||
scanner := scanner.New(opts.MusicPath, opts.DB)
|
scanner := scanner.New(opts.MusicPath, opts.DB, opts.GenreSplit)
|
||||||
jukebox := jukebox.New(opts.MusicPath)
|
jukebox := jukebox.New(opts.MusicPath)
|
||||||
// the base controller, it's fields/middlewares are embedded/used by the
|
// the base controller, it's fields/middlewares are embedded/used by the
|
||||||
// other two admin ui and subsonic controllers
|
// other two admin ui and subsonic controllers
|
||||||
|
|||||||
Reference in New Issue
Block a user