diff --git a/go.mod b/go.mod index a4d9686..677b3ff 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/sentriz/gonic require ( cloud.google.com/go v0.37.1 // indirect github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 // indirect - github.com/dhowden/tag v0.0.0-20181104225729-a9f04c2798ca + github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d // indirect github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect github.com/go-sql-driver/mysql v1.4.1 // indirect github.com/gobuffalo/genny v0.1.1 // indirect @@ -19,6 +19,7 @@ require ( github.com/karrick/godirwalk v1.8.0 github.com/lib/pq v1.0.0 // indirect github.com/mattn/go-sqlite3 v1.10.0 // indirect + github.com/mdlayher/taggolib v0.0.0-20140723044655-d71b09674cfe github.com/peterbourgon/ff v1.2.0 github.com/pkg/errors v0.8.1 github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be @@ -28,7 +29,4 @@ require ( golang.org/x/sys v0.0.0-20190422165155-953cdadca894 // indirect golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b // indirect google.golang.org/appengine v1.5.0 // indirect - gopkg.in/axiomzen/null.v3 v3.2.4 - gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect - gopkg.in/pg.v4 v4.9.5 // indirect ) diff --git a/go.sum b/go.sum index c74e204..f6af258 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 h1:tTngnoO/B6HQnJ+pK8tN7kEAhmhIfaJOutqq/A4/JTM= github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= -github.com/dhowden/tag v0.0.0-20181104225729-a9f04c2798ca h1:EsPh1VImRZ6OOhWtz/zzwTjxVQKcKIiqS5tYNdx2eCg= -github.com/dhowden/tag v0.0.0-20181104225729-a9f04c2798ca/go.mod h1:SniNVYuaD1jmdEEvi+7ywb1QFR7agjeTdGKyFb0p7Rw= +github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d h1:HB5J9+f1xpkYLgWQ/RqEcbp3SEufyOIMYLoyKNKiG7E= +github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d/go.mod h1:CHkHWWZ4kbGY6jEy1+qlitDaCtRgNvCOQdakj/1Yl/Q= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -132,6 +132,8 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdlayher/taggolib v0.0.0-20140723044655-d71b09674cfe h1:WyohQTx/GamBIn8NQAH3JrnEk5Tj++eNmwJOexXxNIk= +github.com/mdlayher/taggolib v0.0.0-20140723044655-d71b09674cfe/go.mod h1:11hji841NkL7BD+0rOgSuBzQ7bxBHKUj7Tp01NFZTE0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= @@ -263,16 +265,10 @@ google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9M google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/axiomzen/null.v3 v3.2.4 h1:5VmJ9lSU0dBJjisXhuhRnGiIolhTjkmQQ0EBNE9Z5QY= -gopkg.in/axiomzen/null.v3 v3.2.4/go.mod h1:Vq8/79AVvSZVg5PdN4kNROnTff7WtSh0HOiBHfOvVNU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/pg.v4 v4.9.5 h1:bs21aaMPPPcUPhNtqGxN8EeYUFU10MsNrC7U9m/lJgU= -gopkg.in/pg.v4 v4.9.5/go.mod h1:cSUPtzgofjgARAbFCE5u6WDHGPgbR1sjUYcWQlKvpec= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/model/model.go b/model/model.go index 540e2e3..c555efd 100644 --- a/model/model.go +++ b/model/model.go @@ -24,13 +24,9 @@ type Track struct { Duration int `gorm:"not null" sql:"default: null"` Size int `gorm:"not null" sql:"default: null"` Bitrate int `gorm:"not null" sql:"default: null"` - TagDiscNumber int `sql:"default: null"` TagTitle string `sql:"default: null"` - TagTotalDiscs int `sql:"default: null"` - TagTotalTracks int `sql:"default: null"` TagTrackArtist string `sql:"default: null"` TagTrackNumber int `sql:"default: null"` - TagYear int `sql:"default: null"` } func (t *Track) Ext() string { @@ -74,15 +70,16 @@ type Play struct { type Album struct { IDBase CrudBase - LeftPath string `gorm:"unique_index:idx_left_path_right_path"` - RightPath string `gorm:"not null; unique_index:idx_left_path_right_path" sql:"default: null"` - Parent *Album - ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"` - Cover string `sql:"default: null"` - TagArtist *Artist - TagArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"` - TagTitle string `gorm:"index" sql:"default: null"` - TagYear int `sql:"default: null"` - Tracks []*Track - IsNew bool `gorm:"-"` + LeftPath string `gorm:"unique_index:idx_left_path_right_path"` + RightPath string `gorm:"not null; unique_index:idx_left_path_right_path" sql:"default: null"` + Parent *Album + ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"` + Cover string `sql:"default: null"` + TagArtist *Artist + TagArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"` + TagTitle string `gorm:"index" sql:"default: null"` + TagYear int `sql:"default: null"` + Tracks []*Track + ReceivedPaths bool `gorm:"-"` + ReceivedTags bool `gorm:"-"` } diff --git a/scanner/scanner.go b/scanner/scanner.go index 622141a..5502a70 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -9,7 +9,6 @@ import ( "sync/atomic" "time" - "github.com/dhowden/tag" "github.com/jinzhu/gorm" "github.com/karrick/godirwalk" "github.com/pkg/errors" @@ -198,7 +197,7 @@ func (s *Scanner) callbackItem(fullPath string, info *godirwalk.Dirent) error { func (s *Scanner) callbackPost(fullPath string, info *godirwalk.Dirent) error { folder := s.curFolders.Pop() - if folder.IsNew { + if folder.ReceivedPaths { folder.ParentID = s.curFolderID() folder.Cover = s.curCover s.tx.Save(folder) @@ -209,19 +208,6 @@ func (s *Scanner) callbackPost(fullPath string, info *godirwalk.Dirent) error { return nil } -func readTags(path string) (tag.Metadata, error) { - trackData, err := os.Open(path) - if err != nil { - return nil, errors.Wrap(err, "reading track from disk") - } - defer trackData.Close() - tags, err := tag.ReadFrom(trackData) - if err != nil { - return nil, errors.Wrap(err, "reading tags from track") - } - return tags, nil -} - func (s *Scanner) handleFolder(it *item) error { folder := &model.Album{} defer s.curFolders.Push(folder) @@ -240,7 +226,7 @@ func (s *Scanner) handleFolder(it *item) error { folder.LeftPath = it.directory folder.RightPath = it.filename s.tx.Save(folder) - folder.IsNew = true + folder.ReceivedPaths = true return nil } @@ -268,21 +254,15 @@ func (s *Scanner) handleTrack(it *item) error { track.Filename = it.filename track.Size = int(it.stat.Size()) track.AlbumID = s.curFolderID() - track.Duration = -1 - track.Bitrate = -1 tags, err := readTags(it.fullPath) if err != nil { return errors.Wrap(err, "reading tags") } - trackNumber, totalTracks := tags.Track() - discNumber, totalDiscs := tags.Disc() - track.TagDiscNumber = discNumber - track.TagTotalDiscs = totalDiscs - track.TagTotalTracks = totalTracks - track.TagTrackNumber = trackNumber track.TagTitle = tags.Title() track.TagTrackArtist = tags.Artist() - track.TagYear = tags.Year() + track.TagTrackNumber = tags.TrackNumber() + track.Duration = tags.DurationSecs() // these two should be calculated + track.Bitrate = tags.Bitrate() // from the file instead of tags // // set album artist basics artist := &model.Artist{} @@ -299,11 +279,14 @@ func (s *Scanner) handleTrack(it *item) error { s.seenTracksNew++ // // set album if this is the first track in the folder - if !s.curFolder().IsNew { + folder := s.curFolder() + if !folder.ReceivedPaths || folder.ReceivedTags { + // the folder hasn't been modified or already has it's tags return nil } - s.curFolder().TagTitle = tags.Album() - s.curFolder().TagYear = tags.Year() - s.curFolder().TagArtistID = artist.ID + folder.TagTitle = tags.Album() + folder.TagYear = tags.Year() + folder.TagArtistID = artist.ID + folder.ReceivedTags = true return nil } diff --git a/scanner/tags.go b/scanner/tags.go new file mode 100644 index 0000000..adbabaa --- /dev/null +++ b/scanner/tags.go @@ -0,0 +1,51 @@ +package scanner + +import ( + "os" + "strconv" + "strings" + + "github.com/mdlayher/taggolib" + "github.com/pkg/errors" +) + +type tags struct { + taggolib.Parser +} + +func (t *tags) Year() int { + // for this one there could be multiple tags and a + // date separator. so do string(2019-6-6) -> int(2019) + // for the two options and pick the first + for _, name := range []string{"DATE", "YEAR"} { + tag := t.Tag(name) + if tag == "" { + continue + } + parts := strings.Split(tag, "-") + year, err := strconv.Atoi(parts[0]) + if err != nil { + continue + } + return year + } + return 0 +} + +func (t *tags) DurationSecs() int { + return int(t.Duration() / 1e9) +} + +func readTags(path string) (*tags, error) { + track, err := os.Open(path) + if err != nil { + return nil, errors.Wrap(err, "reading track from disk") + } + defer track.Close() + parser, err := taggolib.New(track) + if err != nil { + return nil, errors.Wrap(err, "reading tags from track") + } + newTags := &tags{parser} + return newTags, nil +} diff --git a/server/handler/construct_sub_by_folder.go b/server/handler/construct_sub_by_folder.go index bc4b2ec..9177a49 100644 --- a/server/handler/construct_sub_by_folder.go +++ b/server/handler/construct_sub_by_folder.go @@ -48,7 +48,8 @@ func newTCTrackByFolder(t *model.Track, parent *model.Album) *subsonic.TrackChil ), ParentID: parent.ID, CoverID: parent.ID, - Duration: 0, + Duration: t.Duration, + Bitrate: t.Bitrate, IsDir: false, Type: "music", } diff --git a/server/handler/construct_sub_by_tags.go b/server/handler/construct_sub_by_tags.go index b285c3e..8cfd65b 100644 --- a/server/handler/construct_sub_by_tags.go +++ b/server/handler/construct_sub_by_tags.go @@ -41,6 +41,8 @@ func newTrackByTags(t *model.Track, album *model.Album) *subsonic.TrackChild { AlbumID: album.ID, ArtistID: album.TagArtist.ID, CoverID: album.ID, + Duration: t.Duration, + Bitrate: t.Bitrate, Type: "music", } } diff --git a/server/handler/handler_sub_common.go b/server/handler/handler_sub_common.go index c6b89c0..5905175 100644 --- a/server/handler/handler_sub_common.go +++ b/server/handler/handler_sub_common.go @@ -78,7 +78,7 @@ func (c *Controller) GetCoverArt(w http.ResponseWriter, r *http.Request) { } folder := &model.Album{} err = c.DB. - Select("id, path, cover"). + Select("id, left_path, right_path, cover"). First(folder, id). Error if gorm.IsRecordNotFoundError(err) { @@ -91,8 +91,8 @@ func (c *Controller) GetCoverArt(w http.ResponseWriter, r *http.Request) { } absPath := path.Join( c.MusicPath, - folder.RightPath, folder.LeftPath, + folder.RightPath, folder.Cover, ) http.ServeFile(w, r, absPath)