Keep embedded cover art when transcoding to mp3 and flac

This commit is contained in:
quepasaquepasa 2026-07-31 00:06:44 -04:00
parent 600ea5482c
commit 6e23ec3455
3 changed files with 63 additions and 4 deletions

View File

@ -166,7 +166,10 @@ var (
Name: "mp3 audio",
TargetFormat: "mp3",
DefaultBitRate: 192,
Command: "ffmpeg -ss %t -i %s -map 0:a:0 -map_metadata 0 -map_metadata 0:s:a:0 -b:a %bk -v 0 -f mp3 -",
// 0:v:0? also carries over embedded cover art, when the source has any. Only
// mp3 and flac are given this treatment: the opus and adts muxers reject a
// video stream outright.
Command: "ffmpeg -ss %t -i %s -map 0:a:0 -map 0:v:0? -map_metadata 0 -map_metadata 0:s:a:0 -b:a %bk -v 0 -c:v copy -disposition:v attached_pic -f mp3 -",
},
{
Name: "opus audio",
@ -184,7 +187,7 @@ var (
Name: "flac audio",
TargetFormat: "flac",
DefaultBitRate: 0,
Command: "ffmpeg -ss %t -i %s -map 0:a:0 -map_metadata 0 -map_metadata 0:s:a:0 -v 0 -c:a flac -f flac -",
Command: "ffmpeg -ss %t -i %s -map 0:a:0 -map 0:v:0? -map_metadata 0 -map_metadata 0:s:a:0 -v 0 -c:a flac -c:v copy -disposition:v attached_pic -f flac -",
},
}
)

View File

@ -83,7 +83,7 @@ var _ = Describe("ffmpeg", func() {
Describe("isDefaultCommand", func() {
It("returns true for known default mp3 command", func() {
Expect(isDefaultCommand("mp3", "ffmpeg -ss %t -i %s -map 0:a:0 -map_metadata 0 -map_metadata 0:s:a:0 -b:a %bk -v 0 -f mp3 -")).To(BeTrue())
Expect(isDefaultCommand("mp3", "ffmpeg -ss %t -i %s -map 0:a:0 -map 0:v:0? -map_metadata 0 -map_metadata 0:s:a:0 -b:a %bk -v 0 -c:v copy -disposition:v attached_pic -f mp3 -")).To(BeTrue())
})
It("returns true for known default opus command", func() {
Expect(isDefaultCommand("opus", "ffmpeg -ss %t -i %s -map 0:a:0 -map_metadata 0 -map_metadata 0:s:a:0 -b:a %bk -v 0 -c:a libopus -f opus -")).To(BeTrue())
@ -92,7 +92,7 @@ var _ = Describe("ffmpeg", func() {
Expect(isDefaultCommand("aac", "ffmpeg -ss %t -i %s -map 0:a:0 -map_metadata 0 -map_metadata 0:s:a:0 -b:a %bk -v 0 -c:a aac -f adts -")).To(BeTrue())
})
It("returns true for known default flac command", func() {
Expect(isDefaultCommand("flac", "ffmpeg -ss %t -i %s -map 0:a:0 -map_metadata 0 -map_metadata 0:s:a:0 -v 0 -c:a flac -f flac -")).To(BeTrue())
Expect(isDefaultCommand("flac", "ffmpeg -ss %t -i %s -map 0:a:0 -map 0:v:0? -map_metadata 0 -map_metadata 0:s:a:0 -v 0 -c:a flac -c:v copy -disposition:v attached_pic -f flac -")).To(BeTrue())
})
It("returns false for a custom command", func() {
Expect(isDefaultCommand("mp3", "ffmpeg -i %s -b:a %bk -custom-flag -f mp3 -")).To(BeFalse())

View File

@ -0,0 +1,56 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigrationContext(upKeepCoverArtInDefaultTranscodings, downKeepCoverArtInDefaultTranscodings)
}
// coverArtPairs maps the current default commands (audio stream only) to the new
// defaults that also carry over embedded cover art. Index 0 = old, index 1 = new.
//
// `-map 0:v:0?` picks up the attached picture when the source has one, and the
// trailing `?` keeps the command working for sources without artwork. `-c:v copy`
// avoids re-encoding it and `-disposition:v attached_pic` marks it as cover art
// rather than a video track.
//
// Only mp3 and flac are updated: the opus muxer rejects the mjpeg stream
// ("Unsupported codec id in stream 1") and adts refuses any video stream
// ("adts muxer does not support any stream of type video"), so adding the
// mapping there would break transcoding to those formats outright.
//
// Only rows still holding the exact unmodified default are updated, so any
// user-customized command is left untouched.
var coverArtPairs = [][2]string{
{
"ffmpeg -ss %t -i %s -map 0:a:0 -map_metadata 0 -map_metadata 0:s:a:0 -b:a %bk -v 0 -f mp3 -",
"ffmpeg -ss %t -i %s -map 0:a:0 -map 0:v:0? -map_metadata 0 -map_metadata 0:s:a:0 -b:a %bk -v 0 -c:v copy -disposition:v attached_pic -f mp3 -",
},
{
"ffmpeg -ss %t -i %s -map 0:a:0 -map_metadata 0 -map_metadata 0:s:a:0 -v 0 -c:a flac -f flac -",
"ffmpeg -ss %t -i %s -map 0:a:0 -map 0:v:0? -map_metadata 0 -map_metadata 0:s:a:0 -v 0 -c:a flac -c:v copy -disposition:v attached_pic -f flac -",
},
}
func upKeepCoverArtInDefaultTranscodings(ctx context.Context, tx *sql.Tx) error {
for _, p := range coverArtPairs {
if _, err := tx.ExecContext(ctx, `UPDATE transcoding SET command = ? WHERE command = ?`, p[1], p[0]); err != nil {
return err
}
}
return nil
}
func downKeepCoverArtInDefaultTranscodings(ctx context.Context, tx *sql.Tx) error {
for _, p := range coverArtPairs {
if _, err := tx.ExecContext(ctx, `UPDATE transcoding SET command = ? WHERE command = ?`, p[0], p[1]); err != nil {
return err
}
}
return nil
}