feat(subsonic): return transcoded mime and transcoded suffix in subsonic responses

fixes #106

* Added support to TranscodedContentType and TranscodedSuffix

* Make sure that we have a profile

* Fix linting

* Fixed use of NewTCTrackByFolder instead of NewTrackByTags in handlers_by_tags.go

simplify a bit
This commit is contained in:
dertasiu
2022-11-02 23:10:23 +01:00
committed by sentriz
parent 692ec68282
commit 6e6404af73
8 changed files with 137 additions and 46 deletions

View File

@@ -9,12 +9,12 @@ import (
"log"
"net/http"
"go.senan.xyz/gonic/server/ctrlbase"
"go.senan.xyz/gonic/server/ctrlsubsonic/params"
"go.senan.xyz/gonic/server/ctrlsubsonic/spec"
"go.senan.xyz/gonic/jukebox"
"go.senan.xyz/gonic/podcasts"
"go.senan.xyz/gonic/scrobble"
"go.senan.xyz/gonic/server/ctrlbase"
"go.senan.xyz/gonic/server/ctrlsubsonic/params"
"go.senan.xyz/gonic/server/ctrlsubsonic/spec"
"go.senan.xyz/gonic/transcode"
)

View File

@@ -95,6 +95,9 @@ func (c *Controller) ServeGetMusicDirectory(r *http.Request) *spec.Response {
Preload("TrackRating", "user_id=?", user.ID).
Order("filename").
Find(&childTracks)
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for _, ch := range childTracks {
toAppend := spec.NewTCTrackByFolder(ch, folder)
if v, _ := params.Get("c"); v == "Jamstash" {
@@ -102,6 +105,8 @@ func (c *Controller) ServeGetMusicDirectory(r *http.Request) *spec.Response {
toAppend.ContentType = "audio/mpeg"
toAppend.Suffix = "mp3"
}
toAppend.TranscodedContentType = transcodeMIME
toAppend.TranscodedSuffix = transcodeSuffix
childrenObj = append(childrenObj, toAppend)
}
// respond section
@@ -259,8 +264,14 @@ func (c *Controller) ServeSearchTwo(r *http.Request) *spec.Response {
if err := q.Find(&tracks).Error; err != nil {
return spec.NewError(0, "find tracks: %v", err)
}
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for _, t := range tracks {
results.Tracks = append(results.Tracks, spec.NewTCTrackByFolder(t, t.Album))
track := spec.NewTCTrackByFolder(t, t.Album)
track.TranscodedContentType = transcodeMIME
track.TranscodedSuffix = transcodeSuffix
results.Tracks = append(results.Tracks, track)
}
sub := spec.NewResponse()
@@ -335,8 +346,14 @@ func (c *Controller) ServeGetStarred(r *http.Request) *spec.Response {
if err := q.Find(&tracks).Error; err != nil {
return spec.NewError(0, "find tracks: %v", err)
}
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for _, t := range tracks {
results.Tracks = append(results.Tracks, spec.NewTCTrackByFolder(t, t.Album))
track := spec.NewTCTrackByFolder(t, t.Album)
track.TranscodedContentType = transcodeMIME
track.TranscodedSuffix = transcodeSuffix
results.Tracks = append(results.Tracks, track)
}
sub := spec.NewResponse()

View File

@@ -117,8 +117,13 @@ func (c *Controller) ServeGetAlbum(r *http.Request) *spec.Response {
sub := spec.NewResponse()
sub.Album = spec.NewAlbumByTags(album, album.TagArtist)
sub.Album.Tracks = make([]*spec.TrackChild, len(album.Tracks))
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for i, track := range album.Tracks {
sub.Album.Tracks[i] = spec.NewTrackByTags(track, album)
sub.Album.Tracks[i].TranscodedContentType = transcodeMIME
sub.Album.Tracks[i].TranscodedSuffix = transcodeSuffix
}
return sub
}
@@ -270,8 +275,14 @@ func (c *Controller) ServeSearchThree(r *http.Request) *spec.Response {
if err := q.Find(&tracks).Error; err != nil {
return spec.NewError(0, "find tracks: %v", err)
}
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for _, t := range tracks {
results.Tracks = append(results.Tracks, spec.NewTrackByTags(t, t.Album))
track := spec.NewTrackByTags(t, t.Album)
track.TranscodedContentType = transcodeMIME
track.TranscodedSuffix = transcodeSuffix
results.Tracks = append(results.Tracks, track)
}
sub := spec.NewResponse()
@@ -411,9 +422,15 @@ func (c *Controller) ServeGetSongsByGenre(r *http.Request) *spec.Response {
sub.TracksByGenre = &spec.TracksByGenre{
List: make([]*spec.TrackChild, len(tracks)),
}
for i, track := range tracks {
sub.TracksByGenre.List[i] = spec.NewTrackByTags(track, track.Album)
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for i, t := range tracks {
sub.TracksByGenre.List[i] = spec.NewTrackByTags(t, t.Album)
sub.TracksByGenre.List[i].TranscodedContentType = transcodeMIME
sub.TracksByGenre.List[i].TranscodedSuffix = transcodeSuffix
}
return sub
}
@@ -475,8 +492,14 @@ func (c *Controller) ServeGetStarredTwo(r *http.Request) *spec.Response {
if err := q.Find(&tracks).Error; err != nil {
return spec.NewError(0, "find tracks: %v", err)
}
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for _, t := range tracks {
results.Tracks = append(results.Tracks, spec.NewTrackByTags(t, t.Album))
track := spec.NewTrackByTags(t, t.Album)
track.TranscodedContentType = transcodeMIME
track.TranscodedSuffix = transcodeSuffix
results.Tracks = append(results.Tracks, track)
}
sub := spec.NewResponse()
@@ -546,8 +569,13 @@ func (c *Controller) ServeGetTopSongs(r *http.Request) *spec.Response {
sub.TopSongs = &spec.TopSongs{
Tracks: make([]*spec.TrackChild, len(tracks)),
}
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for i, track := range tracks {
sub.TopSongs.Tracks[i] = spec.NewTrackByTags(track, track.Album)
sub.TopSongs.Tracks[i].TranscodedContentType = transcodeMIME
sub.TopSongs.Tracks[i].TranscodedSuffix = transcodeSuffix
}
return sub
}
@@ -612,8 +640,13 @@ func (c *Controller) ServeGetSimilarSongs(r *http.Request) *spec.Response {
sub.SimilarSongs = &spec.SimilarSongs{
Tracks: make([]*spec.TrackChild, len(tracks)),
}
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for i, track := range tracks {
sub.SimilarSongs.Tracks[i] = spec.NewTrackByTags(track, track.Album)
sub.SimilarSongs.Tracks[i].TranscodedContentType = transcodeMIME
sub.SimilarSongs.Tracks[i].TranscodedSuffix = transcodeSuffix
}
return sub
}
@@ -676,8 +709,13 @@ func (c *Controller) ServeGetSimilarSongsTwo(r *http.Request) *spec.Response {
sub.SimilarSongsTwo = &spec.SimilarSongsTwo{
Tracks: make([]*spec.TrackChild, len(tracks)),
}
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for i, track := range tracks {
sub.SimilarSongsTwo.Tracks[i] = spec.NewTrackByTags(track, track.Album)
sub.SimilarSongsTwo.Tracks[i].TranscodedContentType = transcodeMIME
sub.SimilarSongsTwo.Tracks[i].TranscodedSuffix = transcodeSuffix
}
return sub
}

View File

@@ -127,6 +127,7 @@ func (c *Controller) ServeNotFound(r *http.Request) *spec.Response {
}
func (c *Controller) ServeGetPlayQueue(r *http.Request) *spec.Response {
params := r.Context().Value(CtxParams).(params.Params)
user := r.Context().Value(CtxUser).(*db.User)
var queue db.PlayQueue
err := c.DB.
@@ -143,8 +144,12 @@ func (c *Controller) ServeGetPlayQueue(r *http.Request) *spec.Response {
sub.PlayQueue.Current = queue.CurrentSID()
sub.PlayQueue.Changed = queue.UpdatedAt
sub.PlayQueue.ChangedBy = queue.ChangedBy
trackIDs := queue.GetItems()
sub.PlayQueue.List = make([]*spec.TrackChild, len(trackIDs))
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for i, id := range trackIDs {
track := db.Track{}
c.DB.
@@ -154,6 +159,8 @@ func (c *Controller) ServeGetPlayQueue(r *http.Request) *spec.Response {
Preload("TrackRating", "user_id=?", user.ID).
Find(&track)
sub.PlayQueue.List[i] = spec.NewTCTrackByFolder(&track, track.Album)
sub.PlayQueue.List[i].TranscodedContentType = transcodeMIME
sub.PlayQueue.List[i].TranscodedSuffix = transcodeSuffix
}
return sub
}
@@ -241,8 +248,13 @@ func (c *Controller) ServeGetRandomSongs(r *http.Request) *spec.Response {
sub := spec.NewResponse()
sub.RandomTracks = &spec.RandomTracks{}
sub.RandomTracks.List = make([]*spec.TrackChild, len(tracks))
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for i, track := range tracks {
sub.RandomTracks.List[i] = spec.NewTrackByTags(track, track.Album)
sub.RandomTracks.List[i].TranscodedContentType = transcodeMIME
sub.RandomTracks.List[i].TranscodedSuffix = transcodeSuffix
}
return sub
}

View File

@@ -13,7 +13,7 @@ import (
"go.senan.xyz/gonic/server/ctrlsubsonic/spec"
)
func playlistRender(c *Controller, playlist *db.Playlist) *spec.Playlist {
func playlistRender(c *Controller, playlist *db.Playlist, params params.Params) *spec.Playlist {
user := &db.User{}
c.DB.Where("id=?", playlist.UserID).Find(user)
@@ -29,6 +29,9 @@ func playlistRender(c *Controller, playlist *db.Playlist) *spec.Playlist {
trackIDs := playlist.GetItems()
resp.List = make([]*spec.TrackChild, len(trackIDs))
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for i, id := range trackIDs {
track := db.Track{}
err := c.DB.
@@ -44,12 +47,15 @@ func playlistRender(c *Controller, playlist *db.Playlist) *spec.Playlist {
continue
}
resp.List[i] = spec.NewTCTrackByFolder(&track, track.Album)
resp.List[i].TranscodedContentType = transcodeMIME
resp.List[i].TranscodedSuffix = transcodeSuffix
resp.Duration += track.Length
}
return resp
}
func (c *Controller) ServeGetPlaylists(r *http.Request) *spec.Response {
params := r.Context().Value(CtxParams).(params.Params)
user := r.Context().Value(CtxUser).(*db.User)
var playlists []*db.Playlist
c.DB.Where("user_id=?", user.ID).Or("is_public=?", true).Find(&playlists)
@@ -58,7 +64,7 @@ func (c *Controller) ServeGetPlaylists(r *http.Request) *spec.Response {
List: make([]*spec.Playlist, len(playlists)),
}
for i, playlist := range playlists {
sub.Playlists.List[i] = playlistRender(c, playlist)
sub.Playlists.List[i] = playlistRender(c, playlist, params)
}
return sub
}
@@ -78,7 +84,7 @@ func (c *Controller) ServeGetPlaylist(r *http.Request) *spec.Response {
return spec.NewError(70, "playlist with id `%d` not found", playlistID)
}
sub := spec.NewResponse()
sub.Playlist = playlistRender(c, &playlist)
sub.Playlist = playlistRender(c, &playlist, params)
return sub
}
@@ -114,7 +120,7 @@ func (c *Controller) ServeCreatePlaylist(r *http.Request) *spec.Response {
c.DB.Save(playlist)
sub := spec.NewResponse()
sub.Playlist = playlistRender(c, &playlist)
sub.Playlist = playlistRender(c, &playlist, params)
return sub
}

View File

@@ -43,6 +43,18 @@ func streamGetTransPref(dbc *db.DB, userID int, client string) (*db.TranscodePre
return &pref, nil
}
func streamGetTransPrefProfile(dbc *db.DB, userID int, client string) (mime string, suffix string) {
pref, _ := streamGetTransPref(dbc, userID, client)
if pref == nil {
return "", ""
}
profile, ok := transcode.UserProfiles[pref.Profile]
if !ok {
return "", ""
}
return profile.MIME(), profile.Suffix()
}
var errUnknownMediaType = fmt.Errorf("media type is unknown")
// TODO: there is a mismatch between abs paths for podcasts and music. if they were the same, db.AudioFile

View File

@@ -8,6 +8,8 @@ import (
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
)
// https://web.archive.org/web/20220707025402/https://www.subsonic.org/pages/api.jsp
const (
apiVersion = "1.15.0"
xmlns = "http://subsonic.org/restapi"
@@ -135,28 +137,30 @@ type TracksByGenre struct {
}
type TrackChild struct {
ID *specid.ID `xml:"id,attr,omitempty" json:"id,omitempty"`
Album string `xml:"album,attr,omitempty" json:"album,omitempty"`
AlbumID *specid.ID `xml:"albumId,attr,omitempty" json:"albumId,omitempty"`
Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"`
ArtistID *specid.ID `xml:"artistId,attr,omitempty" json:"artistId,omitempty"`
Bitrate int `xml:"bitRate,attr,omitempty" json:"bitRate,omitempty"`
ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"`
CoverID *specid.ID `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"`
CreatedAt time.Time `xml:"created,attr,omitempty" json:"created,omitempty"`
Duration int `xml:"duration,attr,omitempty" json:"duration,omitempty"`
Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"`
IsDir bool `xml:"isDir,attr" json:"isDir"`
IsVideo bool `xml:"isVideo,attr" json:"isVideo"`
ParentID *specid.ID `xml:"parent,attr,omitempty" json:"parent,omitempty"`
Path string `xml:"path,attr,omitempty" json:"path,omitempty"`
Size int `xml:"size,attr,omitempty" json:"size,omitempty"`
Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"`
Title string `xml:"title,attr" json:"title"`
TrackNumber int `xml:"track,attr,omitempty" json:"track,omitempty"`
DiscNumber int `xml:"discNumber,attr,omitempty" json:"discNumber,omitempty"`
Type string `xml:"type,attr,omitempty" json:"type,omitempty"`
Year int `xml:"year,attr,omitempty" json:"year,omitempty"`
ID *specid.ID `xml:"id,attr,omitempty" json:"id,omitempty"`
Album string `xml:"album,attr,omitempty" json:"album,omitempty"`
AlbumID *specid.ID `xml:"albumId,attr,omitempty" json:"albumId,omitempty"`
Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"`
ArtistID *specid.ID `xml:"artistId,attr,omitempty" json:"artistId,omitempty"`
Bitrate int `xml:"bitRate,attr,omitempty" json:"bitRate,omitempty"`
ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"`
TranscodedContentType string `xml:"transcodedContentType,attr,omitempty" json:"transcodedContentType,omitempty"`
CoverID *specid.ID `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"`
CreatedAt time.Time `xml:"created,attr,omitempty" json:"created,omitempty"`
Duration int `xml:"duration,attr,omitempty" json:"duration,omitempty"`
Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"`
IsDir bool `xml:"isDir,attr" json:"isDir"`
IsVideo bool `xml:"isVideo,attr" json:"isVideo"`
ParentID *specid.ID `xml:"parent,attr,omitempty" json:"parent,omitempty"`
Path string `xml:"path,attr,omitempty" json:"path,omitempty"`
Size int `xml:"size,attr,omitempty" json:"size,omitempty"`
Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"`
TranscodedSuffix string `xml:"transcodedSuffix,attr,omitempty" json:"transcodedSuffix,omitempty"`
Title string `xml:"title,attr" json:"title"`
TrackNumber int `xml:"track,attr,omitempty" json:"track,omitempty"`
DiscNumber int `xml:"discNumber,attr,omitempty" json:"discNumber,omitempty"`
Type string `xml:"type,attr,omitempty" json:"type,omitempty"`
Year int `xml:"year,attr,omitempty" json:"year,omitempty"`
// star / rating
Starred *time.Time `xml:"starred,attr,omitempty" json:"starred,omitempty"`
UserRating int `xml:"userRating,attr,omitempty" json:"userRating,omitempty"`

View File

@@ -31,8 +31,8 @@ var UserProfiles = map[string]Profile{
// Store as simple strings, since we may let the user provide their own profiles soon
var (
MP3 = NewProfile("audio/mpeg", 128, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libmp3lame -af "volume=replaygain=track:replaygain_preamp=6dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -metadata replaygain_album_gain= -metadata replaygain_album_peak= -metadata replaygain_track_gain= -metadata replaygain_track_peak= -metadata r128_album_gain= -metadata r128_track_gain= -f mp3 -`)
MP3RG = NewProfile("audio/mpeg", 128, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libmp3lame -af "volume=replaygain=track:replaygain_preamp=6dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -metadata replaygain_album_gain= -metadata replaygain_album_peak= -metadata replaygain_track_gain= -metadata replaygain_track_peak= -metadata r128_album_gain= -metadata r128_track_gain= -f mp3 -`)
MP3 = NewProfile("audio/mpeg", "mp3", 128, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libmp3lame -af "volume=replaygain=track:replaygain_preamp=6dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -metadata replaygain_album_gain= -metadata replaygain_album_peak= -metadata replaygain_track_gain= -metadata replaygain_track_peak= -metadata r128_album_gain= -metadata r128_track_gain= -f mp3 -`)
MP3RG = NewProfile("audio/mpeg", "mp3", 128, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libmp3lame -af "volume=replaygain=track:replaygain_preamp=6dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -metadata replaygain_album_gain= -metadata replaygain_album_peak= -metadata replaygain_track_gain= -metadata replaygain_track_peak= -metadata r128_album_gain= -metadata r128_track_gain= -f mp3 -`)
// this sets a baseline gain which results in the final track being +3~5dB louder than
// Foobar2000's default ReplayGain target volume.
@@ -45,14 +45,14 @@ var (
// on my Ryzen 3600 to transcode an 8-minute FLAC with 2x upsample and RG applied.
//
// -- @spijet
OpusCar = NewProfile("audio/ogg", 96, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libopus -vbr on -af "aresample=96000:resampler=soxr, volume=replaygain=track:replaygain_preamp=15dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -f opus -`)
Opus = NewProfile("audio/ogg", 96, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libopus -vbr on -af "volume=replaygain=track:replaygain_preamp=6dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -metadata replaygain_album_gain= -metadata replaygain_album_peak= -metadata replaygain_track_gain= -metadata replaygain_track_peak= -metadata r128_album_gain= -metadata r128_track_gain= -f opus -`)
OpusRG = NewProfile("audio/ogg", 96, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libopus -vbr on -af "volume=replaygain=track:replaygain_preamp=6dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -metadata replaygain_album_gain= -metadata replaygain_album_peak= -metadata replaygain_track_gain= -metadata replaygain_track_peak= -metadata r128_album_gain= -metadata r128_track_gain= -f opus -`)
Opus128Car = NewProfile("audio/ogg", 128, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libopus -vbr on -af "aresample=96000:resampler=soxr, volume=replaygain=track:replaygain_preamp=15dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -f opus -`)
Opus128 = NewProfile("audio/ogg", 128, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libopus -vbr on -af "volume=replaygain=track:replaygain_preamp=6dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -metadata replaygain_album_gain= -metadata replaygain_album_peak= -metadata replaygain_track_gain= -metadata replaygain_track_peak= -metadata r128_album_gain= -metadata r128_track_gain= -f opus -`)
Opus128RG = NewProfile("audio/ogg", 128, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libopus -vbr on -af "volume=replaygain=track:replaygain_preamp=6dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -metadata replaygain_album_gain= -metadata replaygain_album_peak= -metadata replaygain_track_gain= -metadata replaygain_track_peak= -metadata r128_album_gain= -metadata r128_track_gain= -f opus -`)
OpusCar = NewProfile("audio/ogg", "ogg", 96, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libopus -vbr on -af "aresample=96000:resampler=soxr, volume=replaygain=track:replaygain_preamp=15dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -f opus -`)
Opus = NewProfile("audio/ogg", "ogg", 96, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libopus -vbr on -af "volume=replaygain=track:replaygain_preamp=6dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -metadata replaygain_album_gain= -metadata replaygain_album_peak= -metadata replaygain_track_gain= -metadata replaygain_track_peak= -metadata r128_album_gain= -metadata r128_track_gain= -f opus -`)
OpusRG = NewProfile("audio/ogg", "ogg", 96, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libopus -vbr on -af "volume=replaygain=track:replaygain_preamp=6dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -metadata replaygain_album_gain= -metadata replaygain_album_peak= -metadata replaygain_track_gain= -metadata replaygain_track_peak= -metadata r128_album_gain= -metadata r128_track_gain= -f opus -`)
Opus128Car = NewProfile("audio/ogg", "ogg", 128, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libopus -vbr on -af "aresample=96000:resampler=soxr, volume=replaygain=track:replaygain_preamp=15dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -f opus -`)
Opus128 = NewProfile("audio/ogg", "ogg", 128, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libopus -vbr on -af "volume=replaygain=track:replaygain_preamp=6dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -metadata replaygain_album_gain= -metadata replaygain_album_peak= -metadata replaygain_track_gain= -metadata replaygain_track_peak= -metadata r128_album_gain= -metadata r128_track_gain= -f opus -`)
Opus128RG = NewProfile("audio/ogg", "ogg", 128, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libopus -vbr on -af "volume=replaygain=track:replaygain_preamp=6dB:replaygain_noclip=0, alimiter=level=disabled, asidedata=mode=delete:type=REPLAYGAIN" -metadata replaygain_album_gain= -metadata replaygain_album_peak= -metadata replaygain_track_gain= -metadata replaygain_track_peak= -metadata r128_album_gain= -metadata r128_track_gain= -f opus -`)
PCM16le = NewProfile("audio/wav", 0, `ffmpeg -v 0 -i <file> -ss <seek> -c:a pcm_s16le -ac 2 -f s16le -`)
PCM16le = NewProfile("audio/wav", "wav", 0, `ffmpeg -v 0 -i <file> -ss <seek> -c:a pcm_s16le -ac 2 -f s16le -`)
)
type BitRate int // kb/s
@@ -61,15 +61,17 @@ type Profile struct {
bitrate BitRate // the default bitrate, but the user can request a different one
seek time.Duration
mime string
suffix string
exec string
}
func (p *Profile) BitRate() BitRate { return p.bitrate }
func (p *Profile) Seek() time.Duration { return p.seek }
func (p *Profile) Suffix() string { return p.suffix }
func (p *Profile) MIME() string { return p.mime }
func NewProfile(mime string, bitrate BitRate, exec string) Profile {
return Profile{mime: mime, bitrate: bitrate, exec: exec}
func NewProfile(mime string, suffix string, bitrate BitRate, exec string) Profile {
return Profile{mime: mime, suffix: suffix, bitrate: bitrate, exec: exec}
}
func WithBitrate(p Profile, bitRate BitRate) Profile {