package main import ( "fmt" "os" "os/exec" "path/filepath" "strings" ) // ToolAvailability tracks optional embedding tools found on PATH. type ToolAvailability struct { Metaflac bool ID3v2 bool AtomicParsley bool } func detectEmbeddingTools() ToolAvailability { tools := ToolAvailability{} if _, err := exec.LookPath("metaflac"); err == nil { tools.Metaflac = true } if _, err := exec.LookPath("id3v2"); err == nil { tools.ID3v2 = true } if _, err := exec.LookPath("AtomicParsley"); err == nil { tools.AtomicParsley = true } return tools } func warnMissingEmbeddingTools(tools ToolAvailability) { if !tools.Metaflac { fmt.Println("Warning: metaflac not found. Embedding will be skipped for FLAC files.") } if !tools.ID3v2 { fmt.Println("Warning: id3v2 not found. Embedding will be skipped for MP3 files.") } if !tools.AtomicParsley { fmt.Println("Warning: AtomicParsley not found. Embedding will be skipped for M4A files.") } } func hasEmbeddedLyrics(file string, tools ToolAvailability) (bool, error) { fileExt := strings.ToLower(filepath.Ext(file)) switch fileExt { case ".flac": if !tools.Metaflac { return false, nil } cmd := exec.Command("metaflac", "--export-tags-to=-", file) output, err := cmd.Output() if err != nil { return false, err } tagDump := strings.ToUpper(string(output)) for _, line := range strings.Split(tagDump, "\n") { if strings.HasPrefix(line, "LYRICS=") || strings.HasPrefix(line, "UNSYNCEDLYRICS=") { return true, nil } } return false, nil case ".mp3": if !tools.ID3v2 { return false, nil } cmd := exec.Command("id3v2", "--list", file) output, err := cmd.Output() if err != nil { return false, err } return strings.Contains(string(output), "USLT"), nil case ".m4a": if !tools.AtomicParsley { return false, nil } cmd := exec.Command("AtomicParsley", file, "-t") output, err := cmd.Output() if err != nil { return false, err } return strings.Contains(string(output), "\u00a9lyr"), nil default: return false, fmt.Errorf("checking for embedded lyrics not supported for %s files", fileExt) } } func embedLyrics(file, lyrics, fileExt string, tools ToolAvailability) error { switch strings.ToLower(fileExt) { case ".flac": if !tools.Metaflac { return fmt.Errorf("metaflac command not found") } tmpFile, err := os.CreateTemp("", "lyrics-*.txt") if err != nil { return fmt.Errorf("failed to create temp file: %v", err) } defer os.Remove(tmpFile.Name()) if _, err := tmpFile.WriteString(lyrics); err != nil { return fmt.Errorf("failed to write lyrics to temp file: %v", err) } if err := tmpFile.Close(); err != nil { return fmt.Errorf("failed to close temp file: %v", err) } removeCmd := exec.Command("metaflac", "--remove-tag=LYRICS", "--remove-tag=UNSYNCEDLYRICS", file) if err := removeCmd.Run(); err != nil { return fmt.Errorf("failed to remove existing lyrics: %v", err) } addCmd := exec.Command( "metaflac", "--set-tag-from-file=LYRICS="+tmpFile.Name(), "--set-tag-from-file=UNSYNCEDLYRICS="+tmpFile.Name(), file, ) return addCmd.Run() case ".mp3": if !tools.ID3v2 { return fmt.Errorf("id3v2 command not found") } tmpFile, err := os.CreateTemp("", "lyrics-*.txt") if err != nil { return fmt.Errorf("failed to create temp file: %v", err) } defer os.Remove(tmpFile.Name()) if _, err := tmpFile.WriteString(lyrics); err != nil { return fmt.Errorf("failed to write lyrics to temp file: %v", err) } if err := tmpFile.Close(); err != nil { return fmt.Errorf("failed to close temp file: %v", err) } cmd := exec.Command("id3v2", "--USLT", tmpFile.Name(), file) if err := cmd.Run(); err != nil { return fmt.Errorf("failed to embed lyrics with id3v2: %v", err) } return nil case ".m4a": if !tools.AtomicParsley { return fmt.Errorf("AtomicParsley command not found") } tmpFile, err := os.CreateTemp("", "lyrics-*.txt") if err != nil { return fmt.Errorf("failed to create temp file: %v", err) } defer os.Remove(tmpFile.Name()) if _, err := tmpFile.WriteString(lyrics); err != nil { return fmt.Errorf("failed to write lyrics to temp file: %v", err) } if err := tmpFile.Close(); err != nil { return fmt.Errorf("failed to close temp file: %v", err) } cmd := exec.Command("AtomicParsley", file, "--lyrics", tmpFile.Name(), "--overWrite") if err := cmd.Run(); err != nil { return fmt.Errorf("failed to embed lyrics with AtomicParsley: %v", err) } return nil default: return fmt.Errorf("embedding not supported for %s files", fileExt) } }