package backend import ( "fmt" "os" "strconv" "strings" "github.com/go-flac/flacpicture" "github.com/go-flac/flacvorbis" "github.com/go-flac/go-flac" ) type Metadata struct { Title string Artist string Album string Date string TrackNumber int DiscNumber int ISRC string } func EmbedMetadata(filepath string, metadata Metadata, coverPath string) error { f, err := flac.ParseFile(filepath) if err != nil { return fmt.Errorf("failed to parse FLAC file: %w", err) } var cmtIdx = -1 for idx, block := range f.Meta { if block.Type == flac.VorbisComment { cmtIdx = idx break } } cmt := flacvorbis.New() if metadata.Title != "" { _ = cmt.Add(flacvorbis.FIELD_TITLE, metadata.Title) } if metadata.Artist != "" { _ = cmt.Add(flacvorbis.FIELD_ARTIST, metadata.Artist) } if metadata.Album != "" { _ = cmt.Add(flacvorbis.FIELD_ALBUM, metadata.Album) } if metadata.Date != "" { _ = cmt.Add(flacvorbis.FIELD_DATE, metadata.Date) } if metadata.TrackNumber > 0 { _ = cmt.Add(flacvorbis.FIELD_TRACKNUMBER, strconv.Itoa(metadata.TrackNumber)) } if metadata.DiscNumber > 0 { _ = cmt.Add("DISCNUMBER", strconv.Itoa(metadata.DiscNumber)) } if metadata.ISRC != "" { _ = cmt.Add(flacvorbis.FIELD_ISRC, metadata.ISRC) } cmtBlock := cmt.Marshal() if cmtIdx < 0 { f.Meta = append(f.Meta, &cmtBlock) } else { f.Meta[cmtIdx] = &cmtBlock } if coverPath != "" && fileExists(coverPath) { if err := embedCoverArt(f, coverPath); err != nil { fmt.Printf("Warning: Failed to embed cover art: %v\n", err) } } if err := f.Save(filepath); err != nil { return fmt.Errorf("failed to save FLAC file: %w", err) } return nil } func embedCoverArt(f *flac.File, coverPath string) error { imgData, err := os.ReadFile(coverPath) if err != nil { return fmt.Errorf("failed to read cover image: %w", err) } picture, err := flacpicture.NewFromImageData( flacpicture.PictureTypeFrontCover, "Cover", imgData, "image/jpeg", ) if err != nil { return fmt.Errorf("failed to create picture block: %w", err) } pictureBlock := picture.Marshal() for i := len(f.Meta) - 1; i >= 0; i-- { if f.Meta[i].Type == flac.Picture { f.Meta = append(f.Meta[:i], f.Meta[i+1:]...) } } f.Meta = append(f.Meta, &pictureBlock) return nil } func fileExists(path string) bool { _, err := os.Stat(path) return err == nil } // ReadISRCFromFile reads ISRC metadata from a FLAC file func ReadISRCFromFile(filepath string) (string, error) { if !fileExists(filepath) { return "", fmt.Errorf("file does not exist") } f, err := flac.ParseFile(filepath) if err != nil { return "", fmt.Errorf("failed to parse FLAC file: %w", err) } // Find VorbisComment block for _, block := range f.Meta { if block.Type == flac.VorbisComment { cmt, err := flacvorbis.ParseFromMetaDataBlock(*block) if err != nil { continue } // Get ISRC field isrcValues, err := cmt.Get(flacvorbis.FIELD_ISRC) if err == nil && len(isrcValues) > 0 { return isrcValues[0], nil } } } return "", nil // No ISRC found } // CheckISRCExists checks if a file with the given ISRC already exists in the directory func CheckISRCExists(outputDir string, targetISRC string) (string, bool) { if targetISRC == "" { return "", false } // Read all .flac files in directory entries, err := os.ReadDir(outputDir) if err != nil { return "", false } for _, entry := range entries { if entry.IsDir() { continue } // Check only .flac files filename := entry.Name() if len(filename) < 5 || filename[len(filename)-5:] != ".flac" { continue } filepath := fmt.Sprintf("%s/%s", outputDir, filename) // Read ISRC from file (this will fail for corrupted files) isrc, err := ReadISRCFromFile(filepath) if err != nil { // File is corrupted or unreadable, delete it fmt.Printf("Removing corrupted/unreadable file: %s (error: %v)\n", filepath, err) if removeErr := os.Remove(filepath); removeErr != nil { fmt.Printf("Warning: Failed to remove corrupted file %s: %v\n", filepath, removeErr) } continue } // Compare ISRC (case-insensitive) if isrc != "" && strings.EqualFold(isrc, targetISRC) { return filepath, true } } return "", false }