Audio player
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

158 lines
3.4 KiB

package main
import (
"io/fs"
"io/ioutil"
"log"
"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 {
log.Fatalf("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
}