Merge branch 'feat/yandex-music'

# Conflicts:
#	internal/audio/tag/tagger.go
#	internal/audio/tag/tagger_test.go
#	internal/urlparse/parse_test.go
This commit is contained in:
2026-07-10 18:18:04 +02:00
15 changed files with 1544 additions and 9 deletions
+22 -2
View File
@@ -48,9 +48,10 @@ func (t *Tagger) TagFLAC(path string, meta Metadata, coverPath string) error {
}
ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(path)), ".")
forceMP4Muxer := shouldForceMP4Muxer(path, ext)
tmpPath := taggedTempPath(path)
runTag := func(cover string) ([]byte, error) {
args := buildFFmpegArgs(path, tmpPath, meta, cover, ext)
args := buildFFmpegArgs(path, tmpPath, meta, cover, ext, forceMP4Muxer)
cmd := exec.Command("ffmpeg", args...)
return cmd.CombinedOutput()
}
@@ -73,7 +74,7 @@ func (t *Tagger) TagFLAC(path string, meta Metadata, coverPath string) error {
return nil
}
func buildFFmpegArgs(inputPath, outputPath string, meta Metadata, coverPath, ext string) []string {
func buildFFmpegArgs(inputPath, outputPath string, meta Metadata, coverPath, ext string, forceMP4Muxer bool) []string {
args := []string{"-y", "-i", inputPath}
withCover := coverPath != "" && fileExists(coverPath) && supportsAttachedPicture(ext)
if withCover {
@@ -110,11 +111,30 @@ func buildFFmpegArgs(inputPath, outputPath string, meta Metadata, coverPath, ext
"-metadata", "totaldiscs=",
)
}
if forceMP4Muxer {
args = append(args, "-f", "mp4")
}
args = append(args, outputPath)
return args
}
func shouldForceMP4Muxer(path, ext string) bool {
switch strings.TrimPrefix(strings.ToLower(ext), ".") {
case "m4a", "mp4":
default:
return false
}
if _, err := exec.LookPath("ffprobe"); err != nil {
return false
}
out, err := exec.Command("ffprobe", "-v", "error", "-select_streams", "a:0", "-show_entries", "stream=codec_name", "-of", "default=nokey=1:noprint_wrappers=1", path).Output()
if err != nil {
return false
}
return strings.EqualFold(strings.TrimSpace(string(out)), "flac")
}
func taggedTempPath(path string) string {
ext := filepath.Ext(path)
if ext == "" {
+13 -3
View File
@@ -66,7 +66,7 @@ func TestBuildFFmpegArgsWithCover(t *testing.T) {
if err := os.WriteFile(cover, []byte("x"), 0o644); err != nil {
t.Fatalf("write cover: %v", err)
}
args := buildFFmpegArgs("in.flac", "out.flac", Metadata{Title: "x"}, cover, "flac")
args := buildFFmpegArgs("in.flac", "out.flac", Metadata{Title: "x"}, cover, "flac", false)
foundInput2 := false
foundAttach := false
for i := 0; i < len(args)-1; i++ {
@@ -88,7 +88,7 @@ func TestBuildFFmpegArgsSkipsCoverForUnsupportedContainer(t *testing.T) {
if err := os.WriteFile(cover, []byte("x"), 0o644); err != nil {
t.Fatalf("write cover: %v", err)
}
args := buildFFmpegArgs("in.opus", "out.opus", Metadata{Title: "x"}, cover, "opus")
args := buildFFmpegArgs("in.opus", "out.opus", Metadata{Title: "x"}, cover, "opus", false)
for i := 0; i < len(args)-1; i++ {
if args[i] == "-i" && args[i+1] == cover {
t.Fatalf("unexpected cover input for opus: %v", args)
@@ -97,7 +97,7 @@ func TestBuildFFmpegArgsSkipsCoverForUnsupportedContainer(t *testing.T) {
}
func TestBuildFFmpegArgsClearsDiscTagsWhenRequested(t *testing.T) {
args := buildFFmpegArgs("in.flac", "out.flac", Metadata{Title: "x", OmitDiscTags: true}, "", "flac")
args := buildFFmpegArgs("in.flac", "out.flac", Metadata{Title: "x", OmitDiscTags: true}, "", "flac", false)
want := map[string]bool{
"disc=": false,
"disk=": false,
@@ -118,6 +118,16 @@ func TestBuildFFmpegArgsClearsDiscTagsWhenRequested(t *testing.T) {
}
}
func TestBuildFFmpegArgsForcesMP4Muxer(t *testing.T) {
args := buildFFmpegArgs("in.m4a", "out.m4a", Metadata{Title: "x"}, "", "m4a", true)
for i := 0; i < len(args)-1; i++ {
if args[i] == "-f" && args[i+1] == "mp4" {
return
}
}
t.Fatalf("missing forced mp4 muxer args: %v", args)
}
func TestTaggedTempPathPreservesExtension(t *testing.T) {
if got := taggedTempPath("/tmp/song.flac"); got != "/tmp/song.tmp.flac" {
t.Fatalf("taggedTempPath(flac)=%q", got)