Files
Lyrics/embed.go
2026-04-09 01:42:37 +02:00

196 lines
4.6 KiB
Go

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)
}
}