158 lines
3.4 KiB
Go
158 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/dhowden/tag"
|
|
)
|
|
|
|
type metadata struct {
|
|
Title string
|
|
Artist string
|
|
Album string
|
|
Track int
|
|
Length time.Duration
|
|
}
|
|
|
|
func readMetadata(f *os.File) *metadata {
|
|
var Metadata metadata
|
|
|
|
m, err := tag.ReadFrom(f)
|
|
if err != nil || m.Title() == "" {
|
|
Metadata.Title = strings.TrimSpace(path.Base(f.Name()))
|
|
} else {
|
|
Metadata.Title = strings.TrimSpace(m.Title())
|
|
Metadata.Artist = strings.TrimSpace(m.Artist())
|
|
Metadata.Album = strings.TrimSpace(m.Album())
|
|
Metadata.Track, _ = m.Track()
|
|
|
|
/*
|
|
TODO Too slow
|
|
d := mp3.NewDecoder(f)
|
|
var frame mp3.Frame
|
|
var skipped int
|
|
for {
|
|
err = d.Decode(&frame, &skipped)
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
metadata.Length += frame.Duration()
|
|
}
|
|
*/
|
|
}
|
|
|
|
return &Metadata
|
|
}
|
|
|
|
type libraryEntry struct {
|
|
Name string
|
|
IsDir bool
|
|
Mode fs.FileMode
|
|
Path string
|
|
RealPath string
|
|
Metadata *metadata
|
|
}
|
|
|
|
func (e *libraryEntry) String() string {
|
|
if e.Metadata.Title != "" {
|
|
if e.Metadata.Artist != "" {
|
|
return e.Metadata.Artist + " - " + e.Metadata.Title
|
|
}
|
|
|
|
return e.Metadata.Title
|
|
}
|
|
|
|
return strings.TrimSpace(e.Name)
|
|
}
|
|
|
|
func scanFolder(scanPath string) []*libraryEntry {
|
|
files, err := ioutil.ReadDir(scanPath)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to scan %s: %s", scanPath, err))
|
|
}
|
|
|
|
var entries []*libraryEntry
|
|
for _, fileInfo := range files {
|
|
p := path.Join(scanPath, fileInfo.Name())
|
|
var r string
|
|
|
|
if fileInfo.Mode()&os.ModeSymlink != 0 {
|
|
r, err = os.Readlink(p)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if !path.IsAbs(r) {
|
|
r = path.Join(scanPath, r)
|
|
}
|
|
} else {
|
|
r = p
|
|
}
|
|
|
|
b := path.Base(p)
|
|
if fileInfo.IsDir() || fileInfo.Mode()&os.ModeSymlink != 0 {
|
|
if b != "" && (b[0] != '.' || showHiddenFolders) {
|
|
entries = append(entries, &libraryEntry{Name: fileInfo.Name(), IsDir: fileInfo.IsDir(), Mode: fileInfo.Mode(), Path: p, RealPath: r, Metadata: &metadata{Title: strings.TrimSpace(fileInfo.Name())}})
|
|
}
|
|
|
|
continue
|
|
} else if !supportedFormat(b) {
|
|
continue
|
|
}
|
|
|
|
f, err := os.Open(r)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
Metadata := readMetadata(f)
|
|
f.Close()
|
|
|
|
entries = append(entries, &libraryEntry{Name: fileInfo.Name(), IsDir: fileInfo.IsDir(), Mode: fileInfo.Mode(), Path: p, RealPath: r, Metadata: Metadata})
|
|
}
|
|
|
|
sort.Slice(entries, func(i, j int) bool {
|
|
iDir := entries[i].IsDir || entries[i].Mode&os.ModeSymlink != 0
|
|
jDir := entries[j].IsDir || entries[j].Mode&os.ModeSymlink != 0
|
|
if iDir != jDir {
|
|
return iDir
|
|
}
|
|
|
|
if entries[i].Metadata.Album != "" && strings.ToLower(entries[i].Metadata.Album) == strings.ToLower(entries[j].Metadata.Album) && (entries[i].Metadata.Track > 0 || entries[j].Metadata.Track > 0) {
|
|
return entries[i].Metadata.Track < entries[j].Metadata.Track
|
|
}
|
|
|
|
return strings.ToLower(entries[i].Metadata.Album) < strings.ToLower(entries[j].Metadata.Album) && strings.ToLower(entries[i].Name) < strings.ToLower(entries[j].Name)
|
|
})
|
|
|
|
return entries
|
|
}
|
|
|
|
func scanFolderRecursively(path string) []*libraryEntry {
|
|
var entries []*libraryEntry
|
|
|
|
scanFiles := scanFolder(path)
|
|
for _, entry := range scanFiles {
|
|
if !entry.IsDir && entry.Mode&os.ModeSymlink == 0 {
|
|
continue
|
|
}
|
|
|
|
entries = append(entries, scanFolderRecursively(entry.RealPath)...)
|
|
}
|
|
for _, entry := range scanFiles {
|
|
if entry.IsDir || entry.Mode&os.ModeSymlink != 0 {
|
|
continue
|
|
}
|
|
|
|
entries = append(entries, entry)
|
|
}
|
|
|
|
return entries
|
|
}
|