nix

configuration files that power my machines
Log | Files | Refs | README | LICENSE

commit 0e95664d966b60a45f618f4ed5baa13a50362792
parent a4f200402729dcaf8df05ed912f36a70cc4f15f0
Author: mtmn <miro@haravara.org>
Date:   Tue,  7 Jul 2026 18:36:21 +0200

o2aif: fix missing metadata on aiff output files

Diffstat:
Mmodules/mixins/dotfiles/bin/o2aif | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
1 file changed, 64 insertions(+), 25 deletions(-)

diff --git a/modules/mixins/dotfiles/bin/o2aif b/modules/mixins/dotfiles/bin/o2aif @@ -20,7 +20,7 @@ class ConversionStats: def check_artwork(file_path): - """Return True if the file contains an embedded artwork (video) stream.""" + """Return True if the file contains a video/artwork stream.""" result = subprocess.run( [ "ffprobe", @@ -29,7 +29,7 @@ def check_artwork(file_path): "-select_streams", "v", "-show_entries", - "stream=codec_type", + "stream=codec_type,stream_disposition=attached_pic", "-of", "default=noprint_wrappers=1:nokey=1", str(file_path), @@ -37,7 +37,24 @@ def check_artwork(file_path): capture_output=True, text=True, ) - return "video" in result.stdout + output = result.stdout + return "video" in output or "attached_pic=1" in output + + +COVER_NAMES = ("cover", "folder", "front", "albumart", "album") +COVER_EXTS = (".jpg", ".jpeg", ".png", ".bmp", ".gif", ".webp") + + +def find_external_cover(input_path): + """Return the path to a cover image next to the input, or None.""" + parent = input_path.parent + for name in COVER_NAMES: + for ext in COVER_EXTS: + for variant in (name + ext, name.title() + ext, name.upper() + ext): + candidate = parent / variant + if candidate.is_file(): + return candidate + return None def convert_opus_to_aiff(input_path, delete_original=True): @@ -55,53 +72,75 @@ def convert_opus_to_aiff(input_path, delete_original=True): print(f"Converting {input_path} to {output_path}") + had_embedded_artwork = check_artwork(input_path) + external_cover = None if had_embedded_artwork else find_external_cover(input_path) + try: - result = subprocess.run( + ffmpeg_cmd = [ + "ffmpeg", + "-i", + str(input_path), + ] + + if external_cover: + ffmpeg_cmd.extend(["-i", str(external_cover)]) + + ffmpeg_cmd.extend( [ - "ffmpeg", - "-i", - str(input_path), "-c:a", "pcm_s16be", # 16-bit PCM (AIFF default audio codec) "-map", "0:a", # Map audio stream - "-map", - "0:v?", # Map video (cover art) stream if it exists "-map_metadata", - "0", # Copy all metadata + "0:s:0", # Copy Vorbis-comment tags to global AIFF ID3 metadata "-write_id3v2", "1", # Write ID3v2 chunk (required: AIFF muxer defaults to off) "-id3v2_version", "3", # ID3v2.3 for broad compatibility - "-f", - "aiff", - str(output_path), - ], - check=True, - capture_output=True, + ] ) - # Surface any non-fatal ffmpeg warnings (e.g. the optional 0:v? map - # producing a "no video stream" notice). + if had_embedded_artwork: + ffmpeg_cmd.extend(["-map", "0:v?", "-c:v", "copy"]) + elif external_cover: + ffmpeg_cmd.extend(["-map", "1:v", "-c:v", "copy"]) + + ffmpeg_cmd.extend(["-f", "aiff", str(output_path)]) + + result = subprocess.run(ffmpeg_cmd, check=True, capture_output=True) + + # Surface any non-fatal ffmpeg warnings. if result.stderr: for line in result.stderr.decode(errors="replace").splitlines(): if line.strip().lower().startswith("warning"): print(f" ffmpeg warning: {line.strip()}") - had_artwork = check_artwork(input_path) - if had_artwork: - if check_artwork(output_path): - print(f"Successfully converted {input_path} (artwork preserved)") + has_output_artwork = check_artwork(output_path) + if has_output_artwork: + if had_embedded_artwork: + print( + f"Successfully converted {input_path} (embedded artwork preserved)" + ) + status = "artwork_preserved" + elif external_cover: + print( + f"Successfully converted {input_path} " + f"(external cover {external_cover.name} embedded)" + ) status = "artwork_preserved" else: - # Artwork existed in source but did not make it to the output. - # Keep the original so the user can investigate / re-encode. + print(f"Successfully converted {input_path} (artwork preserved)") + status = "artwork_preserved" + else: + if had_embedded_artwork or external_cover: + # Artwork existed in source or as external file but did not + # make it to the output. Keep the original so the user can + # investigate / re-encode. print( f"Warning: Artwork did not transfer for {input_path}; " "keeping original" ) return True, "artwork_lost" - else: print(f"Successfully converted {input_path}") status = "no_artwork"