From ec53d04730e9f6f047d348f64a5cb54b438e9680 Mon Sep 17 00:00:00 2001 From: sentriz Date: Fri, 5 Apr 2019 14:17:59 +0100 Subject: [PATCH] add stream --- cmd/scanner/main.go | 46 ++++++++++++++++++++++---------------- cmd/server/main.go | 1 + db/model.go | 2 ++ go.mod | 7 +----- go.sum | 15 ------------- handler/media.go | 51 ++++++++++++++++++++++++++++++++++--------- handler/middleware.go | 2 +- subsonic/media.go | 2 +- 8 files changed, 74 insertions(+), 52 deletions(-) diff --git a/cmd/scanner/main.go b/cmd/scanner/main.go index 8042acd..7faf197 100644 --- a/cmd/scanner/main.go +++ b/cmd/scanner/main.go @@ -11,8 +11,8 @@ import ( "time" "github.com/sentriz/gonic/db" - "github.com/sentriz/gonic/tags" + "github.com/dhowden/tag" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/sqlite" "github.com/karrick/godirwalk" @@ -22,11 +22,12 @@ var ( orm *gorm.DB tx *gorm.DB cLastAlbum = &lastAlbum{} - audioExtensions = map[string]bool{ - ".mp3": true, - ".flac": true, - ".aac": true, - ".m4a": true, + audioExtensions = map[string]string{ + "mp3": "audio/mpeg", + "flac": "audio/x-flac", + "aac": "audio/x-aac", + "m4a": "audio/m4a", + "ogg": "audio/ogg", } coverFilenames = map[string]bool{ "cover.png": true, @@ -54,22 +55,21 @@ func (l *lastAlbum) isEmpty() bool { return l.coverPath == "" } -func isAudio(filename string) bool { - ext := strings.ToLower(filepath.Ext(filename)) - _, ok := audioExtensions[ext] - return ok -} - func isCover(filename string) bool { _, ok := coverFilenames[strings.ToLower(filename)] return ok } -func readTags(fullPath string) (tags.Metadata, error) { - tags, err := tags.Read(fullPath) +func readTags(fullPath string) (tag.Metadata, error) { + trackData, err := os.Open(fullPath) if err != nil { return nil, fmt.Errorf("when tags from disk: %v", err) } + defer trackData.Close() + tags, err := tag.ReadFrom(trackData) + if err != nil { + return nil, err + } return tags, nil } @@ -112,7 +112,11 @@ func handleFile(fullPath string, info *godirwalk.Dirent) error { cLastAlbum.coverPath = fullPath // 2nd needed for cover insertion return nil } - if !isAudio(filename) { + longExt := filepath.Ext(filename) + extension := strings.ToLower(longExt[1:]) + // check if us audio and save mime type for later + mime, ok := audioExtensions[extension] + if !ok { return nil } // set track basics @@ -128,13 +132,17 @@ func handleFile(fullPath string, info *godirwalk.Dirent) error { if err != nil { return fmt.Errorf("when reading tags: %v", err) } + trackNumber, totalTracks := tags.Track() + discNumber, TotalDiscs := tags.Disc() track.Path = fullPath track.Title = tags.Title() - track.DiscNumber = uint(tags.Disc()) - track.TotalDiscs = uint(tags.TotalDiscs()) - track.TrackNumber = uint(tags.Track()) - track.TotalTracks = uint(tags.TotalTracks()) + track.DiscNumber = uint(discNumber) + track.TotalDiscs = uint(TotalDiscs) + track.TotalTracks = uint(totalTracks) + track.TrackNumber = uint(trackNumber) track.Year = uint(tags.Year()) + track.Suffix = extension + track.ContentType = mime // set artist { artist := db.Artist{ Name: tags.AlbumArtist(), diff --git a/cmd/server/main.go b/cmd/server/main.go index 98aae34..28445bd 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -40,6 +40,7 @@ func main() { mux.HandleFunc("/rest/getIndexes.view", withWare(cont.GetIndexes)) mux.HandleFunc("/rest/getMusicDirectory.view", withWare(cont.GetMusicDirectory)) mux.HandleFunc("/rest/getCoverArt.view", withWare(cont.GetCoverArt)) + mux.HandleFunc("/rest/stream.view", withWare(cont.Stream)) mux.HandleFunc("/rest/getMusicFolders.view", withWare(cont.GetMusicFolders)) mux.HandleFunc("/rest/getPlaylists.view", withWare(cont.GetPlaylists)) mux.HandleFunc("/rest/getGenres.view", withWare(cont.GetGenres)) diff --git a/db/model.go b/db/model.go index ce46d70..2eda332 100644 --- a/db/model.go +++ b/db/model.go @@ -32,6 +32,8 @@ type Track struct { TotalTracks uint TrackNumber uint Year uint + Suffix string + ContentType string Path string `gorm:"not null;unique_index"` } diff --git a/go.mod b/go.mod index f808abc..c27dac5 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 // indirect + github.com/dhowden/tag v0.0.0-20181104225729-a9f04c2798ca github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect github.com/go-sql-driver/mysql v1.4.1 // indirect github.com/gofrs/uuid v3.2.0+incompatible // indirect @@ -11,15 +11,10 @@ require ( github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect github.com/jinzhu/now v1.0.0 // indirect github.com/karrick/godirwalk v1.8.0 - github.com/labstack/gommon v0.2.8 // indirect github.com/lib/pq v1.0.0 // indirect - github.com/mattn/go-colorable v0.1.1 // indirect - github.com/mattn/go-isatty v0.0.7 // indirect github.com/mattn/go-sqlite3 v1.10.0 // indirect github.com/myesui/uuid v1.0.0 // indirect github.com/twinj/uuid v1.0.0 - github.com/valyala/fasttemplate v1.0.1 // indirect - github.com/wtolson/go-taglib v0.0.0-20180718000046-586eb63c2628 // indirect golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 // indirect google.golang.org/appengine v1.5.0 // indirect ) diff --git a/go.sum b/go.sum index fb2aef5..471d4a0 100644 --- a/go.sum +++ b/go.sum @@ -66,15 +66,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= -github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 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= @@ -102,12 +95,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk= github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/wtolson/go-taglib v0.0.0-20180718000046-586eb63c2628 h1:hYOyf8es7yvMucqlPar3CdobJeY0+0OmR5iT4hHYW54= -github.com/wtolson/go-taglib v0.0.0-20180718000046-586eb63c2628/go.mod h1:p+WHGfN/a+Ol37Pm7EIOO/6Cylieb2qn1jmKfxtSsUg= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= @@ -150,8 +137,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/handler/media.go b/handler/media.go index cd56c2c..2441eb9 100644 --- a/handler/media.go +++ b/handler/media.go @@ -2,7 +2,9 @@ package handler import ( "fmt" + "io" "net/http" + "os" "strconv" "unicode" @@ -89,16 +91,19 @@ func browseAlbum(c *gorm.DB, album *db.Album) *subsonic.Directory { dir.Children = make([]subsonic.Child, len(tracks)) for i, track := range tracks { dir.Children[i] = subsonic.Child{ - ID: track.ID, - Title: track.Title, - Parent: album.ID, - Artist: artist.Name, - ArtistID: artist.ID, - Album: album.Title, - AlbumID: album.ID, - IsDir: false, - Path: track.Path, - CoverArt: cover.ID, + ID: track.ID, + Title: track.Title, + Parent: album.ID, + Artist: artist.Name, + ArtistID: artist.ID, + Album: album.Title, + AlbumID: album.ID, + IsDir: false, + Path: track.Path, + CoverArt: cover.ID, + ContentType: track.ContentType, + Suffix: track.Suffix, + Duration: 0, } } return &dir @@ -143,6 +148,32 @@ func (c *Controller) GetCoverArt(w http.ResponseWriter, req *http.Request) { w.Write(cover.Image) } +func (c *Controller) Stream(w http.ResponseWriter, req *http.Request) { + idStr := req.URL.Query().Get("id") + if idStr == "" { + respondError(w, req, 10, "please provide an `id` parameter") + return + } + id, _ := strconv.Atoi(idStr) + var track db.Track + c.DB.First(&track, id) + if track.Path == "" { + respondError(w, req, 70, fmt.Sprintf("media with id `%d` was not found", id)) + return + } + file, err := os.Open(track.Path) + if err != nil { + respondError(w, req, 0, fmt.Sprintf("error while streaming media: %v", err)) + return + } + stat, _ := file.Stat() + size := strconv.FormatInt(stat.Size(), 10) + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Length", size) + file.Seek(0, 0) + io.Copy(w, file) +} + func (c *Controller) GetMusicFolders(w http.ResponseWriter, req *http.Request) {} func (c *Controller) GetPlaylists(w http.ResponseWriter, req *http.Request) {} func (c *Controller) GetGenres(w http.ResponseWriter, req *http.Request) {} diff --git a/handler/middleware.go b/handler/middleware.go index fd20bba..5a2d6e0 100644 --- a/handler/middleware.go +++ b/handler/middleware.go @@ -32,7 +32,7 @@ func checkCredentialsOldWay(password, givenPassword string) bool { func (c *Controller) LogConnection(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - log.Printf("connection from %s", r.RemoteAddr) + log.Printf("connection from `%s` for `%s`", r.RemoteAddr, r.URL) next.ServeHTTP(w, r) } } diff --git a/subsonic/media.go b/subsonic/media.go index ccc9a78..7d7ad52 100644 --- a/subsonic/media.go +++ b/subsonic/media.go @@ -90,7 +90,7 @@ type Child struct { Size uint `xml:"size,attr,omitempty" json:"size,omitempty"` ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"` Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"` - Duration uint `xml:"duration,attr,omitempty" json:"duration,omitempty"` + Duration uint `xml:"duration,attr,omitempty" json:"duration"` BitRate uint `xml:"bitRate,attr,omitempty" json:"bitrate,omitempty"` Path string `xml:"path,attr,omitempty" json:"path,omitempty"` }