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.
This commit is contained in:
quepasaquepasa 2026-07-31 23:27:19 -04:00
parent 6e23ec3455
commit 68cde4d35b
2 changed files with 34 additions and 0 deletions

View File

@ -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

View File

@ -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",