From 68cde4d35b1588f1a8e1d64d050bd210c550cadc Mon Sep 17 00:00:00 2001 From: quepasaquepasa <293237286+quepasaquepasa@users.noreply.github.com> Date: Fri, 31 Jul 2026 23:27:19 -0400 Subject: [PATCH] Map cover art in the dynamic command builder too isDefaultCommand routes unmodified defaults to buildDynamicArgs, which builds the argument list programmatically and never reads the stored command, so changing the default template alone left artwork stripped for exactly the users the change was aimed at. --- core/ffmpeg/ffmpeg.go | 16 ++++++++++++++++ core/ffmpeg/ffmpeg_test.go | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/core/ffmpeg/ffmpeg.go b/core/ffmpeg/ffmpeg.go index af2dab647..0ff77a77a 100644 --- a/core/ffmpeg/ffmpeg.go +++ b/core/ffmpeg/ffmpeg.go @@ -441,6 +441,14 @@ var formatOutputMap = map[string]string{ "flac": "flac", } +// formatKeepsCoverArt lists the target formats whose muxer accepts an attached +// picture. opus ("Unsupported codec id in stream 1") and adts ("adts muxer does +// not support any stream of type video") reject one, so artwork is dropped there. +var formatKeepsCoverArt = map[string]bool{ + "mp3": true, + "flac": true, +} + // defaultCommands is used to detect whether a user has customized their transcoding command. var defaultCommands = func() map[string]string { m := make(map[string]string, len(consts.DefaultTranscodings)) @@ -468,6 +476,14 @@ func buildDynamicArgs(opts TranscodeOptions) []string { args = append(args, "-i", opts.FilePath) args = append(args, "-map", "0:a:0") + // Carry over embedded cover art, when the source has any. The trailing "?" + // keeps the mapping optional so sources without artwork still transcode. + // Only mp3 and flac: the opus muxer rejects the mjpeg stream and adts + // refuses any video stream, so mapping it there breaks transcoding outright. + if formatKeepsCoverArt[opts.Format] { + args = append(args, "-map", "0:v:0?", "-c:v", "copy", "-disposition:v", "attached_pic") + } + // Preserve source tags. -map_metadata 0 copies format-level tags (MP3/FLAC); // -map_metadata 0:s:a:0 copies tags from the first audio stream (OPUS/OGG). // Both are needed because the two source families store tags at different diff --git a/core/ffmpeg/ffmpeg_test.go b/core/ffmpeg/ffmpeg_test.go index 3912bc2e6..b8dc653e0 100644 --- a/core/ffmpeg/ffmpeg_test.go +++ b/core/ffmpeg/ffmpeg_test.go @@ -114,6 +114,7 @@ var _ = Describe("ffmpeg", func() { Expect(args).To(Equal([]string{ "ffmpeg", "-i", "/music/file.flac", "-map", "0:a:0", + "-map", "0:v:0?", "-c:v", "copy", "-disposition:v", "attached_pic", "-map_metadata", "0", "-map_metadata", "0:s:a:0", "-c:a", "libmp3lame", "-b:a", "256k", @@ -134,6 +135,7 @@ var _ = Describe("ffmpeg", func() { Expect(args).To(Equal([]string{ "ffmpeg", "-i", "/music/file.dsf", "-map", "0:a:0", + "-map", "0:v:0?", "-c:v", "copy", "-disposition:v", "attached_pic", "-map_metadata", "0", "-map_metadata", "0:s:a:0", "-c:a", "flac", "-ar", "48000", @@ -161,6 +163,20 @@ var _ = Describe("ffmpeg", func() { })) }) + It("does not map cover art for muxers that reject it", func() { + // The opus and adts muxers refuse a video stream outright, so mapping + // the attached picture there would break transcoding rather than + // preserve artwork. + for _, format := range []string{"opus", "aac"} { + args := buildDynamicArgs(TranscodeOptions{ + Format: format, + FilePath: "/music/file.flac", + BitRate: 128, + }) + Expect(strings.Join(args, " ")).ToNot(ContainSubstring("0:v:0?"), format) + } + }) + It("includes offset when specified", func() { args := buildDynamicArgs(TranscodeOptions{ Format: "mp3", @@ -173,6 +189,7 @@ var _ = Describe("ffmpeg", func() { "-ss", "30", "-i", "/music/file.mp3", "-map", "0:a:0", + "-map", "0:v:0?", "-c:v", "copy", "-disposition:v", "attached_pic", "-map_metadata", "0", "-map_metadata", "0:s:a:0", "-c:a", "libmp3lame", "-b:a", "192k", @@ -209,6 +226,7 @@ var _ = Describe("ffmpeg", func() { Expect(args).To(Equal([]string{ "ffmpeg", "-i", "/music/file.dsf", "-map", "0:a:0", + "-map", "0:v:0?", "-c:v", "copy", "-disposition:v", "attached_pic", "-map_metadata", "0", "-map_metadata", "0:s:a:0", "-c:a", "flac", "-sample_fmt", "s32",