Fix Opus cover art and quality: map audio only, embed art via Mutagen
Opus ffmpeg command now uses -map 0:a to extract only the audio stream, matching the user's working command and preventing picture stream interference with the encoder. After conversion, cover art is embedded from the source FLAC using Mutagen: Opus gets METADATA_BLOCK_PICTURE (base64-encoded per OGG spec), AAC gets MP4Cover. Applied to both the post-download lossy copy and the repair job fix handler.
This commit is contained in:
parent
b0b1c22f80
commit
aca8a8e996
2 changed files with 74 additions and 2 deletions
|
|
@ -1928,7 +1928,7 @@ class RepairWorker:
|
|||
|
||||
codec_configs = {
|
||||
'mp3': ('libmp3lame', '.mp3', ['-id3v2_version', '3']),
|
||||
'opus': ('libopus', '.opus', ['-vbr', 'on']),
|
||||
'opus': ('libopus', '.opus', ['-map', '0:a', '-vbr', 'on']),
|
||||
'aac': ('aac', '.m4a', ['-movflags', '+faststart']),
|
||||
}
|
||||
|
||||
|
|
@ -1997,6 +1997,39 @@ class RepairWorker:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# Embed cover art from source FLAC
|
||||
if codec in ('opus', 'aac'):
|
||||
try:
|
||||
from mutagen import File as MutagenFile
|
||||
from mutagen.flac import FLAC as MutagenFLAC
|
||||
source_audio = MutagenFLAC(resolved)
|
||||
if source_audio and source_audio.pictures:
|
||||
pic = source_audio.pictures[0]
|
||||
dest_audio = MutagenFile(out_path)
|
||||
if dest_audio is not None:
|
||||
if codec == 'opus':
|
||||
import base64, struct
|
||||
from mutagen.oggopus import OggOpus
|
||||
if isinstance(dest_audio, OggOpus):
|
||||
picture_data = (
|
||||
struct.pack('>II', pic.type, len(pic.mime.encode('utf-8')))
|
||||
+ pic.mime.encode('utf-8')
|
||||
+ struct.pack('>I', len(pic.desc.encode('utf-8')))
|
||||
+ pic.desc.encode('utf-8')
|
||||
+ struct.pack('>IIII', pic.width, pic.height, pic.depth, pic.colors)
|
||||
+ struct.pack('>I', len(pic.data))
|
||||
+ pic.data
|
||||
)
|
||||
dest_audio['METADATA_BLOCK_PICTURE'] = [base64.b64encode(picture_data).decode('ascii')]
|
||||
dest_audio.save()
|
||||
elif codec == 'aac':
|
||||
from mutagen.mp4 import MP4Cover
|
||||
fmt = MP4Cover.FORMAT_JPEG if 'jpeg' in pic.mime else MP4Cover.FORMAT_PNG
|
||||
dest_audio['covr'] = [MP4Cover(pic.data, imageformat=fmt)]
|
||||
dest_audio.save()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Blasphemy Mode — uses the job's own setting, not the global lossy_copy one
|
||||
delete_original = False
|
||||
if self._config_manager:
|
||||
|
|
|
|||
|
|
@ -14749,7 +14749,7 @@ def _create_lossy_copy(final_path):
|
|||
# Codec configuration: (ffmpeg_codec, extension, quality_label, extra_args)
|
||||
codec_map = {
|
||||
'mp3': ('libmp3lame', '.mp3', f'MP3-{bitrate}', ['-id3v2_version', '3']),
|
||||
'opus': ('libopus', '.opus', f'OPUS-{bitrate}', ['-vbr', 'on']),
|
||||
'opus': ('libopus', '.opus', f'OPUS-{bitrate}', ['-map', '0:a', '-vbr', 'on']),
|
||||
'aac': ('aac', '.m4a', f'AAC-{bitrate}', ['-movflags', '+faststart']),
|
||||
}
|
||||
|
||||
|
|
@ -14808,6 +14808,45 @@ def _create_lossy_copy(final_path):
|
|||
except Exception as tag_err:
|
||||
print(f"⚠️ [Lossy Copy] Could not update QUALITY tag: {tag_err}")
|
||||
|
||||
# Embed cover art from source FLAC into the lossy copy
|
||||
# Opus/OGG can't inherit FLAC cover art via ffmpeg -map_metadata alone
|
||||
if codec in ('opus', 'aac'):
|
||||
try:
|
||||
from mutagen import File as MutagenFile
|
||||
from mutagen.flac import FLAC as MutagenFLAC
|
||||
source_audio = MutagenFLAC(final_path)
|
||||
if source_audio and source_audio.pictures:
|
||||
pic = source_audio.pictures[0]
|
||||
dest_audio = MutagenFile(out_path)
|
||||
if dest_audio is not None:
|
||||
if codec == 'opus':
|
||||
import base64
|
||||
from mutagen.oggopus import OggOpus
|
||||
if isinstance(dest_audio, OggOpus):
|
||||
# OGG stores pictures as base64-encoded METADATA_BLOCK_PICTURE
|
||||
import struct
|
||||
# Build METADATA_BLOCK_PICTURE block
|
||||
picture_data = (
|
||||
struct.pack('>II', pic.type, len(pic.mime.encode('utf-8')))
|
||||
+ pic.mime.encode('utf-8')
|
||||
+ struct.pack('>I', len(pic.desc.encode('utf-8')))
|
||||
+ pic.desc.encode('utf-8')
|
||||
+ struct.pack('>IIII', pic.width, pic.height, pic.depth, pic.colors)
|
||||
+ struct.pack('>I', len(pic.data))
|
||||
+ pic.data
|
||||
)
|
||||
dest_audio['METADATA_BLOCK_PICTURE'] = [base64.b64encode(picture_data).decode('ascii')]
|
||||
dest_audio.save()
|
||||
print(f"🎨 [Lossy Copy] Embedded cover art in Opus file")
|
||||
elif codec == 'aac':
|
||||
from mutagen.mp4 import MP4Cover
|
||||
fmt = MP4Cover.FORMAT_JPEG if 'jpeg' in pic.mime else MP4Cover.FORMAT_PNG
|
||||
dest_audio['covr'] = [MP4Cover(pic.data, imageformat=fmt)]
|
||||
dest_audio.save()
|
||||
print(f"🎨 [Lossy Copy] Embedded cover art in M4A file")
|
||||
except Exception as art_err:
|
||||
print(f"⚠️ [Lossy Copy] Could not embed cover art: {art_err}")
|
||||
|
||||
# Blasphemy Mode: delete original FLAC if enabled and output is verified
|
||||
if config_manager.get('lossy_copy.delete_original', False):
|
||||
try:
|
||||
|
|
|
|||
Loading…
Reference in a new issue