first commit
This commit is contained in:
195
embed.go
Normal file
195
embed.go
Normal file
@@ -0,0 +1,195 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user