i love slop

This commit is contained in:
Gabe Farrell 2026-04-04 00:21:03 -04:00
parent 853f08221f
commit 1d69eeb573
9 changed files with 975 additions and 85 deletions

View file

@ -5,8 +5,116 @@ import (
"log"
"os"
"path/filepath"
"time"
)
// StepStatus records the outcome of a single pipeline step for an album.
type StepStatus struct {
Skipped bool
Err error
}
func (s StepStatus) Failed() bool { return s.Err != nil }
// MetadataSource identifies which backend resolved the album metadata.
type MetadataSource string
const (
MetadataSourceBeets MetadataSource = "beets"
MetadataSourceMusicBrainz MetadataSource = "musicbrainz"
MetadataSourceFileTags MetadataSource = "file_tags"
MetadataSourceUnknown MetadataSource = ""
)
// LyricsStats summarises per-track lyric discovery for an album.
type LyricsStats struct {
Total int // total audio tracks examined
Synced int // tracks with synced (timestamped) LRC lyrics downloaded
Plain int // tracks with plain (un-timestamped) lyrics downloaded
AlreadyHad int // tracks that already had an .lrc file, skipped
NotFound int // tracks for which no lyrics could be found
}
func (l LyricsStats) Downloaded() int { return l.Synced + l.Plain }
// CoverArtStats records what happened with cover art for an album.
type CoverArtStats struct {
Found bool // a cover image file was found in the folder
Embedded bool // cover was successfully embedded into tracks
Source string // filename of the cover image, e.g. "cover.jpg"
}
// AlbumResult holds the outcome of every pipeline step for one imported album.
type AlbumResult struct {
Name string
Path string
Metadata *MusicMetadata
MetadataSource MetadataSource
LyricsStats LyricsStats
CoverArtStats CoverArtStats
TrackCount int
CleanTags StepStatus
TagMetadata StepStatus
Lyrics StepStatus
ReplayGain StepStatus
CoverArt StepStatus
Move StepStatus
// FatalStep is the name of the step that caused the album to be skipped
// entirely, or empty if the album completed the full pipeline.
FatalStep string
}
func (a *AlbumResult) skippedAt(step string) {
a.FatalStep = step
}
func (a *AlbumResult) Succeeded() bool { return a.FatalStep == "" }
func (a *AlbumResult) HasWarnings() bool {
if a.CleanTags.Failed() ||
a.TagMetadata.Failed() ||
a.Lyrics.Failed() ||
a.ReplayGain.Failed() ||
a.CoverArt.Failed() ||
a.Move.Failed() {
return true
} else {
return false
}
}
// ImportSession holds the results of a single importer run.
type ImportSession struct {
StartedAt time.Time
FinishedAt time.Time
Albums []*AlbumResult
}
func (s *ImportSession) Failed() []*AlbumResult {
var out []*AlbumResult
for _, a := range s.Albums {
if !a.Succeeded() {
out = append(out, a)
}
}
return out
}
func (s *ImportSession) WithWarnings() []*AlbumResult {
var out []*AlbumResult
for _, a := range s.Albums {
if a.Succeeded() && a.HasWarnings() {
out = append(out, a)
}
}
return out
}
// lastSession is populated at the end of each RunImporter call.
var lastSession *ImportSession
func RunImporter() {
importDir := os.Getenv("IMPORT_DIR")
libraryDir := os.Getenv("LIBRARY_DIR")
@ -29,6 +137,12 @@ func RunImporter() {
return
}
session := &ImportSession{StartedAt: time.Now()}
defer func() {
session.FinishedAt = time.Now()
lastSession = session
}()
fmt.Println("=== Starting Import ===")
if err := cluster(importDir); err != nil {
@ -60,32 +174,62 @@ func RunImporter() {
fmt.Println("\n===== Album:", e.Name(), "=====")
result := &AlbumResult{Name: e.Name(), Path: albumPath}
session.Albums = append(session.Albums, result)
result.TrackCount = len(tracks)
fmt.Println("→ Cleaning album tags:")
if err = cleanAlbumTags(albumPath); err != nil {
fmt.Println("Cleaning album tags failed:", err)
result.CleanTags.Err = cleanAlbumTags(albumPath)
if result.CleanTags.Failed() {
fmt.Println("Cleaning album tags failed:", result.CleanTags.Err)
}
fmt.Println("→ Tagging album metadata:")
md, err := getAlbumMetadata(albumPath, tracks[0])
md, src, err := getAlbumMetadata(albumPath, tracks[0])
result.TagMetadata.Err = err
result.MetadataSource = src
if err != nil {
fmt.Println("Metadata failed, skipping album:", err)
result.skippedAt("TagMetadata")
continue
}
result.Metadata = md
fmt.Println("→ Fetching synced lyrics from LRCLIB:")
if err := DownloadAlbumLyrics(albumPath); err != nil {
lyricsStats, err := DownloadAlbumLyrics(albumPath)
result.Lyrics.Err = err
result.LyricsStats = lyricsStats
if result.Lyrics.Failed() {
fmt.Println("Failed to download synced lyrics.")
}
fmt.Println("→ Applying ReplayGain to album:", albumPath)
if err := applyReplayGain(albumPath); err != nil {
fmt.Println("ReplayGain failed, skipping album:", err)
result.ReplayGain.Err = applyReplayGain(albumPath)
if result.ReplayGain.Failed() {
fmt.Println("ReplayGain failed, skipping album:", result.ReplayGain.Err)
result.skippedAt("ReplayGain")
continue
}
fmt.Println("→ Downloading cover art for album:", albumPath)
if _, err := FindCoverImage(albumPath); err != nil {
if err := DownloadCoverArt(albumPath, md); err != nil {
fmt.Println("Cover art download failed:", err)
}
}
fmt.Println("→ Embedding cover art for album:", albumPath)
if err := EmbedAlbumArtIntoFolder(albumPath); err != nil {
fmt.Println("Cover embed failed, skipping album:", err)
result.CoverArt.Err = EmbedAlbumArtIntoFolder(albumPath)
if coverImg, err := FindCoverImage(albumPath); err == nil {
result.CoverArtStats.Found = true
result.CoverArtStats.Source = filepath.Base(coverImg)
if result.CoverArt.Err == nil {
result.CoverArtStats.Embedded = true
}
}
if result.CoverArt.Failed() {
fmt.Println("Cover embed failed, skipping album:", result.CoverArt.Err)
result.skippedAt("CoverArt")
continue
}
@ -93,6 +237,7 @@ func RunImporter() {
for _, track := range tracks {
if err := moveToLibrary(libraryDir, md, track); err != nil {
fmt.Println("Failed to move track:", track, err)
result.Move.Err = err // retains last error; all attempts are still made
}
}
@ -102,6 +247,7 @@ func RunImporter() {
for _, file := range lyrics {
if err := moveToLibrary(libraryDir, md, file); err != nil {
fmt.Println("Failed to move lyrics:", file, err)
result.Move.Err = err
}
}
@ -109,6 +255,7 @@ func RunImporter() {
if coverImg, err := FindCoverImage(albumPath); err == nil {
if err := moveToLibrary(libraryDir, md, coverImg); err != nil {
fmt.Println("Failed to cover image:", coverImg, err)
result.Move.Err = err
}
}