use user selected profile for transcoding
This commit is contained in:
@@ -57,6 +57,7 @@ then start with `docker-compose up -d`
|
|||||||
|env var|command line arg|description|
|
|env var|command line arg|description|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|`GONIC_MUSIC_PATH`|`-music-path`|path to your music collection|
|
|`GONIC_MUSIC_PATH`|`-music-path`|path to your music collection|
|
||||||
|
|`GONIC_CACHE_PATH`|`-cache-path`|**optional** path to store audio transcodes (*default* `/tmp/gonic_cache`)|
|
||||||
|`GONIC_DB_PATH`|`-db-path`|**optional** path to database file|
|
|`GONIC_DB_PATH`|`-db-path`|**optional** path to database file|
|
||||||
|`GONIC_LISTEN_ADDR`|`-listen-addr`|**optional** host and port to listen on (eg. `0.0.0.0:4747`, `127.0.0.1:4747`) (*default* `0.0.0.0:4747`)|
|
|`GONIC_LISTEN_ADDR`|`-listen-addr`|**optional** host and port to listen on (eg. `0.0.0.0:4747`, `127.0.0.1:4747`) (*default* `0.0.0.0:4747`)|
|
||||||
|`GONIC_PROXY_PREFIX`|`-proxy-prefix`|**optional** url path prefix to use if behind reverse proxy. eg `/gonic` (see example configs below)|
|
|`GONIC_PROXY_PREFIX`|`-proxy-prefix`|**optional** url path prefix to use if behind reverse proxy. eg `/gonic` (see example configs below)|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ var Bytes = map[string]*EmbeddedAsset{
|
|||||||
0x7b,0x20,0x65,0x6e,0x64,0x20,0x7d,0x7d,0x0a,
|
0x7b,0x20,0x65,0x6e,0x64,0x20,0x7d,0x7d,0x0a,
|
||||||
}},
|
}},
|
||||||
"pages/home.tmpl": &EmbeddedAsset{
|
"pages/home.tmpl": &EmbeddedAsset{
|
||||||
ModTime: time.Unix(1583954752, 0),
|
ModTime: time.Unix(1583972849, 0),
|
||||||
Bytes: []byte{
|
Bytes: []byte{
|
||||||
0x7b,0x7b,0x20,0x64,0x65,0x66,0x69,0x6e,0x65,0x20,0x22,0x75,0x73,0x65,0x72,0x22,0x20,0x7d,0x7d,0x0a,0x3c,0x64,0x69,0x76,
|
0x7b,0x7b,0x20,0x64,0x65,0x66,0x69,0x6e,0x65,0x20,0x22,0x75,0x73,0x65,0x72,0x22,0x20,0x7d,0x7d,0x0a,0x3c,0x64,0x69,0x76,
|
||||||
0x20,0x63,0x6c,0x61,0x73,0x73,0x3d,0x22,0x70,0x61,0x64,0x64,0x65,0x64,0x20,0x62,0x6f,0x78,0x22,0x3e,0x0a,0x20,0x20,0x20,
|
0x20,0x63,0x6c,0x61,0x73,0x73,0x3d,0x22,0x70,0x61,0x64,0x64,0x65,0x64,0x20,0x62,0x6f,0x78,0x22,0x3e,0x0a,0x20,0x20,0x20,
|
||||||
|
|||||||
11
db/model.go
11
db/model.go
@@ -94,6 +94,17 @@ func (t *Track) MIME() string {
|
|||||||
return mime.Types[ext]
|
return mime.Types[ext]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Track) RelPath() string {
|
||||||
|
if t.Album == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return path.Join(
|
||||||
|
t.Album.LeftPath,
|
||||||
|
t.Album.RightPath,
|
||||||
|
t.Filename,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int `gorm:"primary_key"`
|
ID int `gorm:"primary_key"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
|
|||||||
@@ -10,32 +10,11 @@ import (
|
|||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
|
|
||||||
"senan.xyz/g/gonic/db"
|
"senan.xyz/g/gonic/db"
|
||||||
"senan.xyz/g/gonic/mime"
|
|
||||||
"senan.xyz/g/gonic/server/ctrlsubsonic/params"
|
"senan.xyz/g/gonic/server/ctrlsubsonic/params"
|
||||||
"senan.xyz/g/gonic/server/ctrlsubsonic/spec"
|
"senan.xyz/g/gonic/server/ctrlsubsonic/spec"
|
||||||
"senan.xyz/g/gonic/server/encode"
|
"senan.xyz/g/gonic/server/encode"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Put special clients that can't handle Opus here:
|
|
||||||
func encodeProfileFor(client string) string {
|
|
||||||
switch client {
|
|
||||||
case "Soundwaves":
|
|
||||||
return "mp3_rg"
|
|
||||||
case "Jamstash":
|
|
||||||
return "opus_rg"
|
|
||||||
default:
|
|
||||||
return "opus"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileExists(filename string) bool {
|
|
||||||
info, err := os.Stat(filename)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return !info.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
// "raw" handlers are ones that don't always return a spec response.
|
// "raw" handlers are ones that don't always return a spec response.
|
||||||
// it could be a file, stream, etc. so you must either
|
// it could be a file, stream, etc. so you must either
|
||||||
// a) write to response writer
|
// a) write to response writer
|
||||||
@@ -69,6 +48,49 @@ func (c *Controller) ServeGetCoverArt(w http.ResponseWriter, r *http.Request) *s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fileExists(filename string) bool {
|
||||||
|
info, err := os.Stat(filename)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !info.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
type serveTrackOptions struct {
|
||||||
|
track *db.Track
|
||||||
|
pref *db.TranscodePreference
|
||||||
|
maxBitrate int
|
||||||
|
cachePath string
|
||||||
|
musicPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveTrackRaw(w http.ResponseWriter, r *http.Request, opts serveTrackOptions) {
|
||||||
|
log.Printf("serving raw %q\n", opts.track.Filename)
|
||||||
|
w.Header().Set("Content-Type", opts.track.MIME())
|
||||||
|
trackPath := path.Join(opts.musicPath, opts.track.RelPath())
|
||||||
|
http.ServeFile(w, r, trackPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveTrackEncode(w http.ResponseWriter, r *http.Request, opts serveTrackOptions) {
|
||||||
|
profile := encode.Profiles[opts.pref.Profile]
|
||||||
|
bitrate := encode.GetBitrate(opts.maxBitrate, profile)
|
||||||
|
trackPath := path.Join(opts.musicPath, opts.track.RelPath())
|
||||||
|
cacheKey := encode.CacheKey(trackPath, opts.pref.Profile, bitrate)
|
||||||
|
cacheFile := path.Join(opts.cachePath, cacheKey)
|
||||||
|
if fileExists(cacheFile) {
|
||||||
|
log.Printf("serving transcode `%s`: cache [%s/%s] hit!\n", opts.track.Filename, profile.Format, bitrate)
|
||||||
|
http.ServeFile(w, r, cacheFile)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("serving transcode `%s`: cache [%s/%s] miss!\n", opts.track.Filename, profile.Format, bitrate)
|
||||||
|
if err := encode.Encode(w, trackPath, cacheFile, profile, bitrate); err != nil {
|
||||||
|
log.Printf("error encoding %q: %v\n", trackPath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("serving transcode `%s`: encoded to [%s/%s] successfully\n",
|
||||||
|
opts.track.Filename, profile.Format, bitrate)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Controller) ServeStream(w http.ResponseWriter, r *http.Request) *spec.Response {
|
func (c *Controller) ServeStream(w http.ResponseWriter, r *http.Request) *spec.Response {
|
||||||
params := r.Context().Value(CtxParams).(params.Params)
|
params := r.Context().Value(CtxParams).(params.Params)
|
||||||
id, err := params.GetInt("id")
|
id, err := params.GetInt("id")
|
||||||
@@ -83,8 +105,8 @@ func (c *Controller) ServeStream(w http.ResponseWriter, r *http.Request) *spec.R
|
|||||||
if gorm.IsRecordNotFoundError(err) {
|
if gorm.IsRecordNotFoundError(err) {
|
||||||
return spec.NewError(70, "media with id `%d` was not found", id)
|
return spec.NewError(70, "media with id `%d` was not found", id)
|
||||||
}
|
}
|
||||||
|
user := r.Context().Value(CtxUser).(*db.User)
|
||||||
defer func() {
|
defer func() {
|
||||||
user := r.Context().Value(CtxUser).(*db.User)
|
|
||||||
play := db.Play{
|
play := db.Play{
|
||||||
AlbumID: track.Album.ID,
|
AlbumID: track.Album.ID,
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
@@ -96,34 +118,24 @@ func (c *Controller) ServeStream(w http.ResponseWriter, r *http.Request) *spec.R
|
|||||||
play.Count++ // for getAlbumList?type=frequent
|
play.Count++ // for getAlbumList?type=frequent
|
||||||
c.DB.Save(&play)
|
c.DB.Save(&play)
|
||||||
}()
|
}()
|
||||||
client := params.GetOr("c", "generic")
|
client := params.GetOr("c", "*")
|
||||||
maxBitrate, err := params.GetInt("maxBitRate")
|
servOpts := serveTrackOptions{
|
||||||
if err != nil {
|
track: track,
|
||||||
maxBitrate = 0
|
musicPath: c.MusicPath,
|
||||||
}
|
}
|
||||||
|
pref := &db.TranscodePreference{}
|
||||||
absPath := path.Join(
|
err = c.DB.
|
||||||
c.MusicPath,
|
Where("user_id=? AND client=? COLLATE NOCASE", user.ID, client).
|
||||||
track.Album.LeftPath,
|
First(pref).
|
||||||
track.Album.RightPath,
|
Error
|
||||||
track.Filename,
|
if gorm.IsRecordNotFoundError(err) {
|
||||||
)
|
serveTrackRaw(w, r, servOpts)
|
||||||
profileName := encodeProfileFor(client)
|
|
||||||
profile := encode.Profiles[profileName]
|
|
||||||
bitrate := encode.GetBitrate(maxBitrate, profile)
|
|
||||||
cacheKey := encode.CacheKey(absPath, profileName, bitrate)
|
|
||||||
cacheFile := path.Join(c.CachePath, cacheKey)
|
|
||||||
if fileExists(cacheFile) {
|
|
||||||
log.Printf("track `%s`: cache [%s/%s] hit!\n", track.Filename, profile.Format, bitrate)
|
|
||||||
http.ServeFile(w, r, cacheFile)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Printf("track `%s`: cache [%s/%s] miss!\n", track.Filename, profile.Format, bitrate)
|
servOpts.pref = pref
|
||||||
if err := encode.Encode(w, absPath, cacheFile, profile, bitrate); err != nil {
|
servOpts.maxBitrate = params.GetIntOr("maxBitRate", 0)
|
||||||
log.Printf("error encoding %q: %v\n", absPath, err)
|
servOpts.cachePath = c.CachePath
|
||||||
}
|
serveTrackEncode(w, r, servOpts)
|
||||||
log.Printf("track `%s`: encoded to [%s/%s] successfully\n",
|
|
||||||
track.Filename, profile.Format, bitrate)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,20 +153,9 @@ func (c *Controller) ServeDownload(w http.ResponseWriter, r *http.Request) *spec
|
|||||||
if gorm.IsRecordNotFoundError(err) {
|
if gorm.IsRecordNotFoundError(err) {
|
||||||
return spec.NewError(70, "media with id `%d` was not found", id)
|
return spec.NewError(70, "media with id `%d` was not found", id)
|
||||||
}
|
}
|
||||||
|
serveTrackRaw(w, r, serveTrackOptions{
|
||||||
absPath := path.Join(
|
track: track,
|
||||||
c.MusicPath,
|
musicPath: c.MusicPath,
|
||||||
track.Album.LeftPath,
|
})
|
||||||
track.Album.RightPath,
|
|
||||||
track.Filename,
|
|
||||||
)
|
|
||||||
if mime, ok := mime.Types[track.Ext()]; ok {
|
|
||||||
w.Header().Set("Content-Type", mime)
|
|
||||||
}
|
|
||||||
http.ServeFile(w, r, absPath)
|
|
||||||
|
|
||||||
//
|
|
||||||
// We don't need to mark album/track as played
|
|
||||||
// if user just downloads a track, so bail out here:
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,9 +132,10 @@ func Encode(out io.Writer, trackPath, cachePath string, profile *Profile, bitrat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate cache key (file name). For, you know, encoded tracks cache.
|
// Generate cache key (file name). For, you know, encoded tracks cache.
|
||||||
func CacheKey(sourcePath string, profile string, bitrate string) string {
|
func CacheKey(sourcePath string, profile, bitrate string) string {
|
||||||
format := Profiles[profile].Format
|
format := Profiles[profile].Format
|
||||||
return fmt.Sprintf("%x-%s-%s.%s", xxhash.Sum64String(sourcePath), profile, bitrate, format)
|
hash := xxhash.Sum64String(sourcePath)
|
||||||
|
return fmt.Sprintf("%x-%s-%s.%s", hash, profile, bitrate, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if client forces bitrate lower than set in profile:
|
// Check if client forces bitrate lower than set in profile:
|
||||||
|
|||||||
Reference in New Issue
Block a user