remove globals
This commit is contained in:
@@ -236,6 +236,7 @@ type Flash struct {
|
|||||||
Type FlashType
|
Type FlashType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gochecknoinits
|
||||||
func init() {
|
func init() {
|
||||||
gob.Register(&Flash{})
|
gob.Register(&Flash{})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func (c *Controller) ServeHome(r *http.Request) *Response {
|
|||||||
c.DB.
|
c.DB.
|
||||||
Where("user_id=?", user.ID).
|
Where("user_id=?", user.ID).
|
||||||
Find(&data.TranscodePreferences)
|
Find(&data.TranscodePreferences)
|
||||||
for profile := range encode.Profiles {
|
for profile := range encode.Profiles() {
|
||||||
data.TranscodeProfiles = append(data.TranscodeProfiles, profile)
|
data.TranscodeProfiles = append(data.TranscodeProfiles, profile)
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func serveTrackRaw(w http.ResponseWriter, r *http.Request, opts serveTrackOption
|
|||||||
}
|
}
|
||||||
|
|
||||||
func serveTrackEncode(w http.ResponseWriter, r *http.Request, opts serveTrackOptions) {
|
func serveTrackEncode(w http.ResponseWriter, r *http.Request, opts serveTrackOptions) {
|
||||||
profile := encode.Profiles[opts.pref.Profile]
|
profile := encode.Profiles()[opts.pref.Profile]
|
||||||
bitrate := encode.GetBitrate(opts.maxBitrate, profile)
|
bitrate := encode.GetBitrate(opts.maxBitrate, profile)
|
||||||
trackPath := path.Join(opts.musicPath, opts.track.RelPath())
|
trackPath := path.Join(opts.musicPath, opts.track.RelPath())
|
||||||
cacheKey := encode.CacheKey(trackPath, opts.pref.Profile, bitrate)
|
cacheKey := encode.CacheKey(trackPath, opts.pref.Profile, bitrate)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"go.senan.xyz/gonic/version"
|
"go.senan.xyz/gonic/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
apiVersion = "1.9.0"
|
apiVersion = "1.9.0"
|
||||||
xmlns = "http://subsonic.org/restapi"
|
xmlns = "http://subsonic.org/restapi"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,9 +31,8 @@ func wrapMigrations(migrs ...gormigrate.Migration) []*gormigrate.Migration {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
func defaultOptions() url.Values {
|
||||||
dbMaxOpenConns = 1
|
return url.Values{
|
||||||
dbOptions = url.Values{
|
|
||||||
// with this, multiple connections share a single data and schema cache.
|
// with this, multiple connections share a single data and schema cache.
|
||||||
// see https://www.sqlite.org/sharedcache.html
|
// see https://www.sqlite.org/sharedcache.html
|
||||||
"cache": {"shared"},
|
"cache": {"shared"},
|
||||||
@@ -43,7 +42,7 @@ var (
|
|||||||
"_journal_mode": {"WAL"},
|
"_journal_mode": {"WAL"},
|
||||||
"_foreign_keys": {"true"},
|
"_foreign_keys": {"true"},
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
type DB struct {
|
type DB struct {
|
||||||
*gorm.DB
|
*gorm.DB
|
||||||
@@ -55,13 +54,13 @@ func New(path string) (*DB, error) {
|
|||||||
Scheme: "file",
|
Scheme: "file",
|
||||||
Opaque: path,
|
Opaque: path,
|
||||||
}
|
}
|
||||||
url.RawQuery = dbOptions.Encode()
|
url.RawQuery = defaultOptions().Encode()
|
||||||
db, err := gorm.Open("sqlite3", url.String())
|
db, err := gorm.Open("sqlite3", url.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("with gorm: %w", err)
|
return nil, fmt.Errorf("with gorm: %w", err)
|
||||||
}
|
}
|
||||||
db.SetLogger(log.New(os.Stdout, "gorm ", 0))
|
db.SetLogger(log.New(os.Stdout, "gorm ", 0))
|
||||||
db.DB().SetMaxOpenConns(dbMaxOpenConns)
|
db.DB().SetMaxOpenConns(1)
|
||||||
migrOptions := &gormigrate.Options{
|
migrOptions := &gormigrate.Options{
|
||||||
TableName: "migrations",
|
TableName: "migrations",
|
||||||
IDColumnName: "id",
|
IDColumnName: "id",
|
||||||
@@ -69,13 +68,13 @@ func New(path string) (*DB, error) {
|
|||||||
UseTransaction: false,
|
UseTransaction: false,
|
||||||
}
|
}
|
||||||
migr := gormigrate.New(db, migrOptions, wrapMigrations(
|
migr := gormigrate.New(db, migrOptions, wrapMigrations(
|
||||||
migrationInitSchema,
|
migrateInitSchema(),
|
||||||
migrationCreateInitUser,
|
migrateCreateInitUser(),
|
||||||
migrationMergePlaylist,
|
migrateMergePlaylist(),
|
||||||
migrationCreateTranscode,
|
migrateCreateTranscode(),
|
||||||
migrationAddGenre,
|
migrateAddGenre(),
|
||||||
migrationUpdateTranscodePrefIDX,
|
migrateUpdateTranscodePrefIDX(),
|
||||||
migrationAddAlbumIDX,
|
migrateAddAlbumIDX(),
|
||||||
))
|
))
|
||||||
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)
|
||||||
|
|||||||
@@ -9,55 +9,58 @@ import (
|
|||||||
|
|
||||||
// $ date '+%Y%m%d%H%M'
|
// $ date '+%Y%m%d%H%M'
|
||||||
|
|
||||||
// not really a migration
|
func migrateInitSchema() gormigrate.Migration {
|
||||||
var migrationInitSchema = gormigrate.Migration{
|
return gormigrate.Migration{
|
||||||
ID: "202002192100",
|
ID: "202002192100",
|
||||||
Migrate: func(tx *gorm.DB) error {
|
Migrate: func(tx *gorm.DB) error {
|
||||||
return tx.AutoMigrate(
|
return tx.AutoMigrate(
|
||||||
Artist{},
|
Artist{},
|
||||||
Track{},
|
Track{},
|
||||||
User{},
|
User{},
|
||||||
Setting{},
|
Setting{},
|
||||||
Play{},
|
Play{},
|
||||||
Album{},
|
Album{},
|
||||||
Playlist{},
|
Playlist{},
|
||||||
PlayQueue{},
|
PlayQueue{},
|
||||||
).
|
).
|
||||||
Error
|
Error
|
||||||
},
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// not really a migration
|
func migrateCreateInitUser() gormigrate.Migration {
|
||||||
var migrationCreateInitUser = gormigrate.Migration{
|
return gormigrate.Migration{
|
||||||
ID: "202002192019",
|
ID: "202002192019",
|
||||||
Migrate: func(tx *gorm.DB) error {
|
Migrate: func(tx *gorm.DB) error {
|
||||||
const (
|
const (
|
||||||
initUsername = "admin"
|
initUsername = "admin"
|
||||||
initPassword = "admin"
|
initPassword = "admin"
|
||||||
)
|
)
|
||||||
err := tx.
|
err := tx.
|
||||||
Where("name=?", initUsername).
|
Where("name=?", initUsername).
|
||||||
First(&User{}).
|
First(&User{}).
|
||||||
Error
|
Error
|
||||||
if !gorm.IsRecordNotFoundError(err) {
|
if !gorm.IsRecordNotFoundError(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return tx.Create(&User{
|
return tx.Create(&User{
|
||||||
Name: initUsername,
|
Name: initUsername,
|
||||||
Password: initPassword,
|
Password: initPassword,
|
||||||
IsAdmin: true,
|
IsAdmin: true,
|
||||||
}).
|
}).
|
||||||
Error
|
Error
|
||||||
},
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var migrationMergePlaylist = gormigrate.Migration{
|
func migrateMergePlaylist() gormigrate.Migration {
|
||||||
ID: "202002192222",
|
return gormigrate.Migration{
|
||||||
Migrate: func(tx *gorm.DB) error {
|
ID: "202002192222",
|
||||||
if !tx.HasTable("playlist_items") {
|
Migrate: func(tx *gorm.DB) error {
|
||||||
return nil
|
if !tx.HasTable("playlist_items") {
|
||||||
}
|
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
|
||||||
@@ -66,78 +69,87 @@ var migrationMergePlaylist = gormigrate.Migration{
|
|||||||
ORDER BY created_at
|
ORDER BY created_at
|
||||||
) );
|
) );
|
||||||
DROP TABLE playlist_items;`,
|
DROP TABLE playlist_items;`,
|
||||||
).
|
).
|
||||||
Error
|
Error
|
||||||
},
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var migrationCreateTranscode = gormigrate.Migration{
|
func migrateCreateTranscode() gormigrate.Migration {
|
||||||
ID: "202003111222",
|
return gormigrate.Migration{
|
||||||
Migrate: func(tx *gorm.DB) error {
|
ID: "202003111222",
|
||||||
return tx.AutoMigrate(
|
Migrate: func(tx *gorm.DB) error {
|
||||||
TranscodePreference{},
|
return tx.AutoMigrate(
|
||||||
).
|
TranscodePreference{},
|
||||||
Error
|
).
|
||||||
},
|
Error
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var migrationAddGenre = gormigrate.Migration{
|
func migrateAddGenre() gormigrate.Migration {
|
||||||
ID: "202003121330",
|
return gormigrate.Migration{
|
||||||
Migrate: func(tx *gorm.DB) error {
|
ID: "202003121330",
|
||||||
return tx.AutoMigrate(
|
Migrate: func(tx *gorm.DB) error {
|
||||||
Genre{},
|
return tx.AutoMigrate(
|
||||||
Album{},
|
Genre{},
|
||||||
Track{},
|
Album{},
|
||||||
).
|
Track{},
|
||||||
Error
|
).
|
||||||
},
|
Error
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var migrationUpdateTranscodePrefIDX = gormigrate.Migration{
|
func migrateUpdateTranscodePrefIDX() gormigrate.Migration {
|
||||||
ID: "202003241509",
|
return gormigrate.Migration{
|
||||||
Migrate: func(tx *gorm.DB) error {
|
ID: "202003241509",
|
||||||
var hasIDX int
|
Migrate: func(tx *gorm.DB) error {
|
||||||
tx.
|
var hasIDX int
|
||||||
Select("1").
|
tx.
|
||||||
Table("sqlite_master").
|
Select("1").
|
||||||
Where("type = ?", "index").
|
Table("sqlite_master").
|
||||||
Where("name = ?", "idx_user_id_client").
|
Where("type = ?", "index").
|
||||||
Count(&hasIDX)
|
Where("name = ?", "idx_user_id_client").
|
||||||
if hasIDX == 1 {
|
Count(&hasIDX)
|
||||||
// index already exists
|
if hasIDX == 1 {
|
||||||
return nil
|
// index already exists
|
||||||
}
|
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)
|
||||||
}
|
}
|
||||||
step = tx.AutoMigrate(
|
step = tx.AutoMigrate(
|
||||||
TranscodePreference{},
|
TranscodePreference{},
|
||||||
)
|
)
|
||||||
if err := step.Error; err != nil {
|
if err := step.Error; err != nil {
|
||||||
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)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var migrationAddAlbumIDX = gormigrate.Migration{
|
func migrateAddAlbumIDX() gormigrate.Migration {
|
||||||
ID: "202004302006",
|
return gormigrate.Migration{
|
||||||
Migrate: func(tx *gorm.DB) error {
|
ID: "202004302006",
|
||||||
return tx.AutoMigrate(
|
Migrate: func(tx *gorm.DB) error {
|
||||||
Album{},
|
return tx.AutoMigrate(
|
||||||
).
|
Album{},
|
||||||
Error
|
).
|
||||||
},
|
Error
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,8 +94,8 @@ func (t *Track) Ext() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Track) MIME() string {
|
func (t *Track) MIME() string {
|
||||||
ext := t.Ext()
|
v, _ := mime.FromExtension(t.Ext())
|
||||||
return mime.Types[ext]
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Track) RelPath() string {
|
func (t *Track) RelPath() string {
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ import (
|
|||||||
"github.com/cespare/xxhash"
|
"github.com/cespare/xxhash"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
buffLen = 4096
|
||||||
|
)
|
||||||
|
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
Format string
|
Format string
|
||||||
Bitrate int
|
Bitrate int
|
||||||
@@ -20,15 +24,14 @@ type Profile struct {
|
|||||||
forceRG bool
|
forceRG bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
func Profiles() map[string]Profile {
|
||||||
Profiles = map[string]*Profile{
|
return map[string]Profile{
|
||||||
"mp3": {"mp3", 128, []string{"-c:a", "libmp3lame"}, false},
|
"mp3": {"mp3", 128, []string{"-c:a", "libmp3lame"}, false},
|
||||||
"mp3_rg": {"mp3", 128, []string{"-c:a", "libmp3lame"}, true},
|
"mp3_rg": {"mp3", 128, []string{"-c:a", "libmp3lame"}, true},
|
||||||
"opus": {"opus", 96, []string{"-c:a", "libopus", "-vbr", "constrained"}, false},
|
"opus": {"opus", 96, []string{"-c:a", "libopus", "-vbr", "constrained"}, false},
|
||||||
"opus_rg": {"opus", 96, []string{"-c:a", "libopus", "-vbr", "constrained"}, true},
|
"opus_rg": {"opus", 96, []string{"-c:a", "libopus", "-vbr", "constrained"}, true},
|
||||||
}
|
}
|
||||||
bufLen = 4096
|
}
|
||||||
)
|
|
||||||
|
|
||||||
// copy command output to http response body using io.copy (simpler, but may increase ttfb)
|
// copy command output to http response body using io.copy (simpler, but may increase ttfb)
|
||||||
//nolint:deadcode,unused
|
//nolint:deadcode,unused
|
||||||
@@ -45,7 +48,7 @@ func copyCmdOutput(out, cache io.Writer, pipeReader io.Reader) {
|
|||||||
// copy command output to http response manually with a buffer (should reduce ttfb)
|
// copy command output to http response manually with a buffer (should reduce ttfb)
|
||||||
//nolint:deadcode,unused
|
//nolint:deadcode,unused
|
||||||
func writeCmdOutput(out, cache io.Writer, pipeReader io.ReadCloser) {
|
func writeCmdOutput(out, cache io.Writer, pipeReader io.ReadCloser) {
|
||||||
buffer := make([]byte, bufLen)
|
buffer := make([]byte, buffLen)
|
||||||
for {
|
for {
|
||||||
n, err := pipeReader.Read(buffer)
|
n, err := pipeReader.Read(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -70,7 +73,7 @@ func writeCmdOutput(out, cache io.Writer, pipeReader io.ReadCloser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pre-format the ffmpeg command with needed options
|
// pre-format the ffmpeg command with needed options
|
||||||
func ffmpegCommand(filePath string, profile *Profile, bitrate string) *exec.Cmd {
|
func ffmpegCommand(filePath string, profile Profile, bitrate string) *exec.Cmd {
|
||||||
args := []string{
|
args := []string{
|
||||||
"-v", "0",
|
"-v", "0",
|
||||||
"-i", filePath,
|
"-i", filePath,
|
||||||
@@ -94,7 +97,7 @@ func ffmpegCommand(filePath string, profile *Profile, bitrate string) *exec.Cmd
|
|||||||
return exec.Command("/usr/bin/ffmpeg", args...) //nolint:gosec
|
return exec.Command("/usr/bin/ffmpeg", args...) //nolint:gosec
|
||||||
}
|
}
|
||||||
|
|
||||||
func Encode(out io.Writer, trackPath, cachePath string, profile *Profile, bitrate string) error {
|
func Encode(out io.Writer, trackPath, cachePath string, profile Profile, bitrate string) error {
|
||||||
// prepare the command and file descriptors
|
// prepare the command and file descriptors
|
||||||
cmd := ffmpegCommand(trackPath, profile, bitrate)
|
cmd := ffmpegCommand(trackPath, profile, bitrate)
|
||||||
pipeReader, pipeWriter := io.Pipe()
|
pipeReader, pipeWriter := io.Pipe()
|
||||||
@@ -126,13 +129,13 @@ func Encode(out io.Writer, trackPath, cachePath string, profile *Profile, bitrat
|
|||||||
|
|
||||||
// CacheKey generates the filename for the new transcode save
|
// CacheKey generates the filename for the new transcode save
|
||||||
func CacheKey(sourcePath string, profile, bitrate string) string {
|
func CacheKey(sourcePath string, profile, bitrate string) string {
|
||||||
format := Profiles[profile].Format
|
format := Profiles()[profile].Format
|
||||||
hash := xxhash.Sum64String(sourcePath)
|
hash := xxhash.Sum64String(sourcePath)
|
||||||
return fmt.Sprintf("%x-%s-%s.%s", hash, profile, bitrate, format)
|
return fmt.Sprintf("%x-%s-%s.%s", hash, profile, bitrate, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBitrate checks if the client forces bitrate lower than set in profile
|
// GetBitrate checks if the client forces bitrate lower than set in profile
|
||||||
func GetBitrate(clientBitrate int, profile *Profile) string {
|
func GetBitrate(clientBitrate int, profile Profile) string {
|
||||||
bitrate := profile.Bitrate
|
bitrate := profile.Bitrate
|
||||||
if clientBitrate != 0 && clientBitrate < bitrate {
|
if clientBitrate != 0 && clientBitrate < bitrate {
|
||||||
bitrate = clientBitrate
|
bitrate = clientBitrate
|
||||||
|
|||||||
@@ -10,16 +10,15 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.senan.xyz/gonic/server/db"
|
"go.senan.xyz/gonic/server/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
baseURL = "https://ws.audioscrobbler.com/2.0/"
|
baseURL = "https://ws.audioscrobbler.com/2.0/"
|
||||||
client = &http.Client{
|
)
|
||||||
Timeout: 10 * time.Second,
|
|
||||||
}
|
var (
|
||||||
ErrLastFM = errors.New("last.fm error")
|
ErrLastFM = errors.New("last.fm error")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ var (
|
|||||||
|
|
||||||
func getParamSignature(params url.Values, secret string) string {
|
func getParamSignature(params url.Values, secret string) string {
|
||||||
// the parameters must be in order before hashing
|
// the parameters must be in order before hashing
|
||||||
paramKeys := make([]string, 0)
|
paramKeys := make([]string, 0, len(params))
|
||||||
for k := range params {
|
for k := range params {
|
||||||
paramKeys = append(paramKeys, k)
|
paramKeys = append(paramKeys, k)
|
||||||
}
|
}
|
||||||
@@ -45,7 +44,7 @@ func getParamSignature(params url.Values, secret string) string {
|
|||||||
func makeRequest(method string, params url.Values) (LastFM, error) {
|
func makeRequest(method string, params url.Values) (LastFM, error) {
|
||||||
req, _ := http.NewRequest(method, baseURL, nil)
|
req, _ := http.NewRequest(method, baseURL, nil)
|
||||||
req.URL.RawQuery = params.Encode()
|
req.URL.RawQuery = params.Encode()
|
||||||
resp, err := client.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return LastFM{}, fmt.Errorf("get: %w", err)
|
return LastFM{}, fmt.Errorf("get: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
package mime
|
package mime
|
||||||
|
|
||||||
var Types = map[string]string{
|
// this package is at such a high level in the hierarchy because
|
||||||
"mp3": "audio/mpeg",
|
// it's used by both `server/db` and `server/scanner`
|
||||||
"flac": "audio/x-flac",
|
|
||||||
"aac": "audio/x-aac",
|
func FromExtension(ext string) (string, bool) {
|
||||||
"m4a": "audio/m4a",
|
types := map[string]string{
|
||||||
"m4b": "audio/m4b",
|
"mp3": "audio/mpeg",
|
||||||
"ogg": "audio/ogg",
|
"flac": "audio/x-flac",
|
||||||
"opus": "audio/ogg",
|
"aac": "audio/x-aac",
|
||||||
|
"m4a": "audio/m4a",
|
||||||
|
"m4b": "audio/m4b",
|
||||||
|
"ogg": "audio/ogg",
|
||||||
|
"opus": "audio/ogg",
|
||||||
|
}
|
||||||
|
v, ok := types[ext]
|
||||||
|
return v, ok
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func decoded(in string) string {
|
|||||||
|
|
||||||
// isScanning acts as an atomic boolean semaphore. we don't
|
// isScanning acts as an atomic boolean semaphore. we don't
|
||||||
// want to have more than one scan going on at a time
|
// want to have more than one scan going on at a time
|
||||||
var isScanning int32
|
var isScanning int32 //nolint:gochecknoglobals
|
||||||
|
|
||||||
func IsScanning() bool {
|
func IsScanning() bool {
|
||||||
return atomic.LoadInt32(&isScanning) == 1
|
return atomic.LoadInt32(&isScanning) == 1
|
||||||
@@ -224,19 +224,23 @@ type item struct {
|
|||||||
stat os.FileInfo
|
stat os.FileInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
var coverFilenames = map[string]struct{}{
|
func isCover(filename string) bool {
|
||||||
"cover.png": {},
|
known := map[string]struct{}{
|
||||||
"cover.jpg": {},
|
"cover.png": {},
|
||||||
"cover.jpeg": {},
|
"cover.jpg": {},
|
||||||
"folder.png": {},
|
"cover.jpeg": {},
|
||||||
"folder.jpg": {},
|
"folder.png": {},
|
||||||
"folder.jpeg": {},
|
"folder.jpg": {},
|
||||||
"album.png": {},
|
"folder.jpeg": {},
|
||||||
"album.jpg": {},
|
"album.png": {},
|
||||||
"album.jpeg": {},
|
"album.jpg": {},
|
||||||
"front.png": {},
|
"album.jpeg": {},
|
||||||
"front.jpg": {},
|
"front.png": {},
|
||||||
"front.jpeg": {},
|
"front.jpg": {},
|
||||||
|
"front.jpeg": {},
|
||||||
|
}
|
||||||
|
_, ok := known[filename]
|
||||||
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// ## begin callbacks
|
// ## begin callbacks
|
||||||
@@ -267,8 +271,8 @@ func (s *Scanner) callbackItem(fullPath string, info *godirwalk.Dirent) error {
|
|||||||
if isDir {
|
if isDir {
|
||||||
return s.handleFolder(it)
|
return s.handleFolder(it)
|
||||||
}
|
}
|
||||||
lowerFilename := strings.ToLower(filename)
|
filenameLow := strings.ToLower(filename)
|
||||||
if _, ok := coverFilenames[lowerFilename]; ok {
|
if isCover(filenameLow) {
|
||||||
s.curCover = filename
|
s.curCover = filename
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -276,7 +280,7 @@ func (s *Scanner) callbackItem(fullPath string, info *godirwalk.Dirent) error {
|
|||||||
if ext == "" {
|
if ext == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if _, ok := mime.Types[ext[1:]]; ok {
|
if _, ok := mime.FromExtension(ext[1:]); ok {
|
||||||
return s.handleTrack(it)
|
return s.handleTrack(it)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user