i don't remember

This commit is contained in:
Gabe Farrell 2026-04-09 00:39:59 -04:00
parent c7d6a088ed
commit 4324de1271
6 changed files with 98 additions and 48 deletions

View file

@ -80,6 +80,23 @@ func searchMBArtists(query string) ([]mbArtist, error) {
return result.Artists, err
}
// getFirstReleaseMBID returns the MBID of the first release listed under a
// release group. This is needed because beets --search-id requires a release
// MBID, not a release group MBID.
// Returns empty string on error so callers can fall back gracefully.
func getFirstReleaseMBID(rgMBID string) string {
var result struct {
Releases []struct {
ID string `json:"id"`
} `json:"releases"`
}
path := fmt.Sprintf("/ws/2/release-group/%s?fmt=json&inc=releases", url.QueryEscape(rgMBID))
if err := mbGet(path, &result); err != nil || len(result.Releases) == 0 {
return ""
}
return result.Releases[0].ID
}
// getMBArtistReleaseGroups returns all Album and EP release groups for an artist,
// paginating through the MusicBrainz browse API with the required 1 req/s rate limit.
func getMBArtistReleaseGroups(artistMBID string) ([]mbReleaseGroup, error) {
@ -136,15 +153,24 @@ func fetchArtist(artistMBID, artistName string, logf func(string)) error {
failed := 0
for i, rg := range groups {
logf(fmt.Sprintf("[%d/%d] %s", i+1, len(groups), rg.Title))
folder, err := fetchRelease(artistName, rg.Title, rg.ID, logf)
// Resolve a release MBID for this release group. beets --search-id
// requires a release MBID; release group MBIDs are not accepted.
time.Sleep(time.Second) // MusicBrainz rate limit
releaseMBID := getFirstReleaseMBID(rg.ID)
if releaseMBID == "" {
logf(fmt.Sprintf(" ↳ warning: could not resolve release MBID for group %s, beets will search by name", rg.ID))
}
folder, err := fetchRelease(artistName, rg.Title, releaseMBID, logf)
if err != nil {
log.Printf("[discover] fetch failed for %q by %s: %v", rg.Title, artistName, err)
logf(fmt.Sprintf(" ↳ failed: %v", err))
failed++
continue
}
registerDownload(rg.ID, artistName, rg.Title, folder, nil)
logf(fmt.Sprintf(" ↳ registered for import (mbid: %s)", rg.ID))
// Key the pending download by release group ID for dedup; beets uses releaseMBID.
registerDownload(rg.ID, releaseMBID, artistName, rg.Title, folder, nil)
logf(fmt.Sprintf(" ↳ registered for import (release mbid: %s)", releaseMBID))
}
if failed > 0 {
@ -290,7 +316,7 @@ func handleDiscoverFetch(w http.ResponseWriter, r *http.Request) {
return
}
log.Printf("[discover] fetch complete for %q by %s, registering for import", body.Album, body.Artist)
registerDownload(body.ID, body.Artist, body.Album, folder, entry)
registerDownload(body.ID, body.ID, body.Artist, body.Album, folder, entry)
// entry.finish is called by the monitor when import completes
}()

View file

@ -9,8 +9,9 @@ import (
"strings"
)
// moveToLibrary moves a file to {libDir}/{artist}/[{date}] {album} [{quality}]/filename.
func moveToLibrary(libDir string, md *MusicMetadata, srcPath string) error {
// albumTargetDir returns the destination directory for an album without
// creating it. Use this to check for an existing import before moving files.
func albumTargetDir(libDir string, md *MusicMetadata) string {
date := md.Date
if date == "" {
date = md.Year
@ -19,7 +20,12 @@ func moveToLibrary(libDir string, md *MusicMetadata, srcPath string) error {
if md.Quality != "" {
albumDir += fmt.Sprintf(" [%s]", md.Quality)
}
targetDir := filepath.Join(libDir, sanitize(md.Artist), sanitize(albumDir))
return filepath.Join(libDir, sanitize(md.Artist), sanitize(albumDir))
}
// moveToLibrary moves a file to {libDir}/{artist}/[{date}] {album} [{quality}]/filename.
func moveToLibrary(libDir string, md *MusicMetadata, srcPath string) error {
targetDir := albumTargetDir(libDir, md)
if err := os.MkdirAll(targetDir, 0755); err != nil {
return err
}

View file

@ -233,33 +233,39 @@ func RunImporter() {
continue
}
fmt.Println("→ Moving tracks into library for album:", albumPath)
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
targetDir := albumTargetDir(libraryDir, md)
if _, err := os.Stat(targetDir); err == nil {
fmt.Println("→ Album already exists in library, skipping move:", targetDir)
result.Move.Skipped = true
} else {
fmt.Println("→ Moving tracks into library for album:", albumPath)
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
}
}
}
lyrics, _ := getLyricFiles(albumPath)
lyrics, _ := getLyricFiles(albumPath)
fmt.Println("→ Moving lyrics into library for album:", albumPath)
for _, file := range lyrics {
if err := moveToLibrary(libraryDir, md, file); err != nil {
fmt.Println("Failed to move lyrics:", file, err)
result.Move.Err = err
fmt.Println("→ Moving lyrics into library for album:", albumPath)
for _, file := range lyrics {
if err := moveToLibrary(libraryDir, md, file); err != nil {
fmt.Println("Failed to move lyrics:", file, err)
result.Move.Err = err
}
}
}
fmt.Println("→ Moving album cover into library for album:", albumPath)
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
fmt.Println("→ Moving album cover into library for album:", albumPath)
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
}
}
}
os.Remove(albumPath)
os.Remove(albumPath)
}
}
fmt.Println("\n=== Import Complete ===")

View file

@ -220,9 +220,10 @@ func tagWithBeets(path, mbid string) error {
defer os.Remove(logPath)
args := []string{"import", "-Cq", "-l", logPath}
if mbid != "" {
args = append(args, "--search-id", mbid)
}
// passing mbid to beet removed temporarily
// if mbid != "" {
// args = append(args, "--search-id", mbid)
// }
args = append(args, path)
if err := runCmd("beet", args...); err != nil {
return err

View file

@ -13,7 +13,8 @@ import (
// pendingDownload tracks a queued slskd download that should be auto-imported
// once all files have transferred successfully.
type pendingDownload struct {
MBID string
ID string // dedup key (release MBID for single fetches; release group MBID for artist fetches)
BeetsMBID string // release MBID passed to beets --search-id (may differ from ID)
Artist string
Album string
Username string // slskd peer username
@ -28,32 +29,35 @@ var (
)
// registerDownload records a queued slskd download for monitoring and eventual
// auto-import. If entry is nil a new fetchEntry is created, keyed by mbid,
// so the frontend can discover it via /discover/fetch/list.
func registerDownload(mbid, artist, album string, folder *albumFolder, entry *fetchEntry) {
// auto-import. id is used as the dedup key; beetsMBID is the release MBID
// forwarded to beets --search-id (may be empty or differ from id).
// If entry is nil a new fetchEntry is created so the frontend can discover it
// via /discover/fetch/list.
func registerDownload(id, beetsMBID, artist, album string, folder *albumFolder, entry *fetchEntry) {
pd := &pendingDownload{
MBID: mbid,
Artist: artist,
Album: album,
Username: folder.Username,
Dir: folder.Dir,
Files: folder.Files,
Entry: entry,
ID: id,
BeetsMBID: beetsMBID,
Artist: artist,
Album: album,
Username: folder.Username,
Dir: folder.Dir,
Files: folder.Files,
Entry: entry,
}
if entry == nil {
e := newFetchEntry(mbid, artist, album)
e := newFetchEntry(id, artist, album)
e.appendLog(fmt.Sprintf("Queued %d files from %s — waiting for download",
len(folder.Files), folder.Username))
pd.Entry = e
}
pendingMu.Lock()
pendingDownloads[mbid] = pd
pendingDownloads[id] = pd
pendingMu.Unlock()
log.Printf("[monitor] registered: %q by %s (mbid: %s, peer: %s, %d files)",
album, artist, mbid, folder.Username, len(folder.Files))
log.Printf("[monitor] registered: %q by %s (id: %s, beets mbid: %s, peer: %s, %d files)",
album, artist, id, beetsMBID, folder.Username, len(folder.Files))
}
// startMonitor launches a background goroutine that periodically checks whether
@ -128,7 +132,7 @@ func checkPendingDownloads() {
// Remove from pending before starting import to avoid double-import.
pendingMu.Lock()
delete(pendingDownloads, pd.MBID)
delete(pendingDownloads, pd.ID)
pendingMu.Unlock()
go importPendingRelease(pd, localDir)
@ -189,7 +193,7 @@ func importPendingRelease(pd *pendingDownload, localDir string) {
entry := pd.Entry
logf := func(msg string) {
entry.appendLog("[import] " + msg)
log.Printf("[monitor/import %s] %s", pd.MBID, msg)
log.Printf("[monitor/import %s] %s", pd.ID, msg)
}
logf(fmt.Sprintf("Starting import from %s", localDir))
@ -215,7 +219,7 @@ func importPendingRelease(pd *pendingDownload, localDir string) {
logf(fmt.Sprintf("Clean tags warning: %v", err))
}
md, src, err := getAlbumMetadata(localDir, tracks[0], pd.MBID)
md, src, err := getAlbumMetadata(localDir, tracks[0], pd.BeetsMBID)
if err != nil {
entry.finish(fmt.Errorf("metadata failed: %w", err))
return
@ -244,6 +248,13 @@ func importPendingRelease(pd *pendingDownload, localDir string) {
}
logf("Cover art embedded")
targetDir := albumTargetDir(libraryDir, md)
if _, err := os.Stat(targetDir); err == nil {
logf(fmt.Sprintf("Album already exists in library, skipping move: %s", targetDir))
entry.finish(nil)
return
}
var moveErr error
for _, track := range tracks {
if err := moveToLibrary(libraryDir, md, track); err != nil {

Binary file not shown.