Created
October 7, 2024 15:05
-
-
Save kristianfreeman/79ced22ee94ee8eca240b223037b6cc0 to your computer and use it in GitHub Desktop.
Fix rockbox cover art
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from mutagen.flac import FLAC, Picture | |
from mutagen.id3 import APIC, ID3, PictureType, ID3NoHeaderError | |
import os | |
import subprocess | |
import sys | |
import time | |
import hashlib | |
num_files = 0 | |
checksum_file = 'checksums.txt' | |
# Function to calculate the MD5 checksum of a file | |
def calculate_md5(file_path): | |
hash_md5 = hashlib.md5() | |
try: | |
with open(file_path, "rb") as f: | |
for chunk in iter(lambda: f.read(4096), b""): | |
hash_md5.update(chunk) | |
return hash_md5.hexdigest() | |
except Exception as e: | |
print(f"Error calculating MD5 for {file_path}: {e}") | |
return None | |
# Load the checksums from a file | |
def load_checksums(checksum_file): | |
checksums = {} | |
if os.path.exists(checksum_file): | |
with open(checksum_file, 'r') as f: | |
for line in f: | |
try: | |
md5, file_path = line.strip().split(' ') | |
checksums[file_path] = md5 | |
except ValueError: | |
continue | |
return checksums | |
# Save the checksums to a file | |
def save_checksums(checksum_file, checksums): | |
try: | |
with open(checksum_file, 'w') as f: | |
for file_path, md5 in checksums.items(): | |
f.write(f"{md5} {file_path}\n") | |
except Exception as e: | |
print(f"Error saving checksums: {e}") | |
# Initialize checksums | |
checksums = load_checksums(checksum_file) | |
updated_checksums = {**checksums} # Initialize with existing checksums | |
def run_fast_scandir(dir, ext): | |
subfolders, files = [], [] | |
for f in os.scandir(dir): | |
if f.is_dir(): | |
subfolders.append(f.path) | |
if f.is_file(): | |
file_ext = os.path.splitext(f.name)[1].lower() | |
file_path = f.path | |
md5 = calculate_md5(file_path) | |
if md5 is None: | |
continue | |
if md5 == checksums.get(file_path): | |
print(f"Skipping {file_path} (already processed)") | |
continue | |
if file_ext in ext: | |
print(f"Processing {file_ext.upper()} file: {file_path}") | |
if file_ext == '.mp3': | |
fix_mp3(file_path) | |
elif file_ext == '.flac': | |
fix_flac(file_path) | |
elif file_ext == '.jpg': | |
fix_jpg(file_path) | |
files.append(file_path) | |
for dir in list(subfolders): | |
sf, f = run_fast_scandir(dir, ext) | |
subfolders.extend(sf) | |
files.extend(f) | |
return subfolders, files | |
def fix_mp3(file): | |
try: | |
tag = ID3(file) | |
except ID3NoHeaderError: | |
return | |
if len(tag.getall("APIC")) > 0: | |
with open("temp.jpg", "wb") as f: | |
f.write(tag.getall("APIC")[0].data) | |
subprocess.run(['magick', '-interlace', 'None', 'temp.jpg', 'temp.jpg']) | |
tag.delall("APIC") | |
with open("temp.jpg", 'rb') as f: | |
tag.add(APIC(3, 'image/jpeg', 3, u'cover', data=f.read())) | |
tag.save() | |
global num_files | |
num_files += 1 | |
# Save checksum after processing | |
md5 = calculate_md5(file) | |
if md5: | |
updated_checksums[file] = md5 | |
save_checksums(checksum_file, updated_checksums) | |
def fix_flac(file): | |
tag = FLAC(file) | |
pics = tag.pictures | |
for p in pics: | |
if p.type == 3: | |
with open("temp.jpg", "wb") as f: | |
f.write(p.data) | |
subprocess.run(['magick', '-interlace', 'None', 'temp.jpg', 'temp.jpg']) | |
tag.clear_pictures() | |
image = Picture() | |
image.type = PictureType.COVER_FRONT | |
image.mime = 'image/jpeg' | |
with open('temp.jpg', 'rb') as f: | |
image.data = f.read() | |
tag.add_picture(image) | |
tag.save(deleteid3=True) | |
global num_files | |
num_files += 1 | |
# Save checksum after processing | |
md5 = calculate_md5(file) | |
if md5: | |
updated_checksums[file] = md5 | |
save_checksums(checksum_file, updated_checksums) | |
def fix_jpg(file): | |
subprocess.run(['magick', '-interlace', 'None', file, file]) | |
global num_files | |
num_files += 1 | |
# Save checksum after processing | |
md5 = calculate_md5(file) | |
if md5: | |
updated_checksums[file] = md5 | |
save_checksums(checksum_file, updated_checksums) | |
def timer(start, end): | |
hours, rem = divmod(end - start, 3600) | |
minutes, seconds = divmod(rem, 60) | |
return "{:0>2}:{:0>2}:{:05.2f}".format(int(hours), int(minutes), seconds) | |
if len(sys.argv) > 1: | |
start = time.time() | |
subfolders, files = run_fast_scandir(sys.argv[1].replace("\\", "/"), {".flac", ".mp3", ".jpg"}) | |
end = time.time() | |
print("\n" + str(num_files) + " files processed in " + timer(start, end)) | |
else: | |
print("You must provide a path between quotes. For example: python fix_albumart.py \"R:\\My Music\"") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment