Verbessere die Cover-Optimierung durch Anpassung der Größenprüfung und -anpassung für bessere Kompatibilität mit Apple Podcasts.

This commit is contained in:
2025-07-05 20:47:35 +02:00
parent c5abfa342b
commit 6172a898c3
2 changed files with 154 additions and 22 deletions

109
fix_covers.py Normal file
View File

@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""
Cover-Optimierungs-Tool
Repariert alle Cover-Bilder im _audio-Verzeichnis für Apple Podcasts Kompatibilität.
"""
from pathlib import Path
import urllib.parse
def fix_all_covers(audio_dir="../httpdocs/_audio"):
"""Optimiert alle Cover-Dateien im Audio-Verzeichnis."""
try:
from PIL import Image
except ImportError:
print("❌ PIL/Pillow nicht installiert!")
print("💡 Installiere mit: uv add pillow")
return False
audio_path = Path(audio_dir)
if not audio_path.exists():
print(f"❌ Audio-Verzeichnis '{audio_dir}' existiert nicht!")
return False
# Finde alle Cover-Dateien
cover_files = list(audio_path.glob("cover_*.jpg"))
print(f"🖼️ {len(cover_files)} Cover-Dateien gefunden")
if not cover_files:
print(" Keine Cover-Dateien zum Optimieren gefunden")
return True
fixed_count = 0
for cover_file in cover_files:
try:
print(f"\n📋 Prüfe: {cover_file.name}")
# Lade das Bild
image = Image.open(cover_file)
original_width, original_height = image.size
print(f" 📐 Aktuelle Größe: {original_width}x{original_height}px")
# Prüfe ob Optimierung nötig ist
needs_fix = False
if original_width < 1400 or original_height < 1400:
print(f" ⚠️ Zu klein (< 1400px)")
needs_fix = True
elif original_width > 3000 or original_height > 3000:
print(f" ⚠️ Zu groß (> 3000px)")
needs_fix = True
elif original_width != original_height:
print(f" ⚠️ Nicht quadratisch")
needs_fix = True
if not needs_fix:
print(f" ✅ Cover ist bereits optimal")
continue
# Bestimme Zielgröße
if original_width < 1400 or original_height < 1400:
target_size = 1400
elif original_width > 3000 or original_height > 3000:
target_size = 3000
else:
target_size = min(original_width, original_height)
if target_size < 1400:
target_size = 1400
elif target_size > 3000:
target_size = 3000
print(f" 🎯 Zielgröße: {target_size}x{target_size}px")
# Mache quadratisch falls nötig
if original_width != original_height:
size = min(original_width, original_height)
left = (original_width - size) // 2
top = (original_height - size) // 2
right = left + size
bottom = top + size
image = image.crop((left, top, right, bottom))
print(f" ✂️ Zugeschnitten auf quadratisch")
# Skaliere auf Zielgröße
image = image.resize((target_size, target_size), Image.Resampling.LANCZOS)
# Konvertiere zu RGB falls nötig
if image.mode in ('RGBA', 'LA', 'P'):
background = Image.new('RGB', image.size, (255, 255, 255))
if image.mode == 'P':
image = image.convert('RGBA')
background.paste(image, mask=image.split()[-1] if image.mode == 'RGBA' else None)
image = background
# Speichere optimiertes Cover
image.save(cover_file, 'JPEG', quality=95, optimize=True)
final_width, final_height = image.size
print(f" ✅ Optimiert: {final_width}x{final_height}px")
fixed_count += 1
except Exception as e:
print(f" ❌ Fehler bei {cover_file.name}: {e}")
print(f"\n🎉 {fixed_count} von {len(cover_files)} Cover-Dateien optimiert!")
return True
if __name__ == "__main__":
fix_all_covers()

View File

@ -132,36 +132,59 @@ class LocalPodcastGenerator:
# Lade das Bild aus den Cover-Daten # Lade das Bild aus den Cover-Daten
image = Image.open(io.BytesIO(metadata['cover_data'])) image = Image.open(io.BytesIO(metadata['cover_data']))
width, height = image.size original_width, original_height = image.size
print(f" 📐 Original Cover-Größe: {original_width}x{original_height}px")
# Apple Podcasts benötigt quadratische Cover zwischen 1400-3000px # Apple Podcasts benötigt quadratische Cover zwischen 1400-3000px
if width < 1400 or height < 1400: # Wähle Zielgröße basierend auf Originalgröße
print(f" ⚠️ Cover zu klein ({width}x{height}px), skaliere auf 1400x1400px") if original_width < 1400 or original_height < 1400:
image = image.resize((1400, 1400), Image.Resampling.LANCZOS) target_size = 1400
elif width > 3000 or height > 3000: print(f" ⚠️ Cover zu klein, skaliere auf {target_size}x{target_size}px")
print(f" ⚠️ Cover zu groß ({width}x{height}px), skaliere auf 3000x3000px") elif original_width > 3000 or original_height > 3000:
image = image.resize((3000, 3000), Image.Resampling.LANCZOS) target_size = 3000
elif width != height: print(f" ⚠️ Cover zu groß, skaliere auf {target_size}x{target_size}px")
print(f" ⚠️ Cover nicht quadratisch ({width}x{height}px), schneide zu") else:
size = min(width, height) # Cover ist in akzeptabler Größe, mache es quadratisch
image = image.crop(((width-size)//2, (height-size)//2, (width+size)//2, (height+size)//2)) target_size = min(original_width, original_height)
if target_size < 1400:
target_size = 1400
elif target_size > 3000:
target_size = 3000
print(f" ✓ Cover-Größe OK, mache quadratisch: {target_size}x{target_size}px")
# Mache das Bild quadratisch (schneide zu oder fülle auf)
if original_width != original_height:
size = min(original_width, original_height)
left = (original_width - size) // 2
top = (original_height - size) // 2
right = left + size
bottom = top + size
image = image.crop((left, top, right, bottom))
print(f" ✂️ Bild zugeschnitten auf quadratisch: {size}x{size}px")
# Skaliere auf Zielgröße
image = image.resize((target_size, target_size), Image.Resampling.LANCZOS)
# Konvertiere zu RGB falls nötig (für JPEG)
if image.mode in ('RGBA', 'LA', 'P'):
background = Image.new('RGB', image.size, (255, 255, 255))
if image.mode == 'P':
image = image.convert('RGBA')
background.paste(image, mask=image.split()[-1] if image.mode == 'RGBA' else None)
image = background
# Speichere das optimierte Cover # Speichere das optimierte Cover
image.save(cover_path, 'JPEG', quality=95, optimize=True) image.save(cover_path, 'JPEG', quality=95, optimize=True)
print(f" 🖼️ Cover optimiert und gespeichert: {cover_filename} ({image.size[0]}x{image.size[1]}px)") final_width, final_height = image.size
print(f" 🖼️ Cover optimiert und gespeichert: {cover_filename} ({final_width}x{final_height}px)")
except ImportError: except ImportError:
print(" ⚠️ PIL/Pillow nicht installiert - Cover wird ohne Optimierung gespeichert") print(" PIL/Pillow nicht installiert - kann Cover nicht optimieren!")
# Fallback: Speichere Cover ohne Optimierung print(" 💡 Installiere mit: uv add pillow")
with open(cover_path, 'wb') as f: return None
f.write(metadata['cover_data'])
print(f" 🖼️ Cover gespeichert: {cover_filename} (unoptimiert)")
except Exception as img_error: except Exception as img_error:
print(f" ⚠️ Fehler bei Cover-Optimierung: {img_error}") print(f" Fehler bei Cover-Optimierung: {img_error}")
# Fallback: Speichere Cover ohne Optimierung return None
with open(cover_path, 'wb') as f:
f.write(metadata['cover_data'])
print(f" 🖼️ Cover gespeichert: {cover_filename} (unoptimiert)")
# Rückgabe der URL zum Cover (URL-encoded für korrekte Links) # Rückgabe der URL zum Cover (URL-encoded für korrekte Links)
cover_url = f"{self.base_url}/_audio/{urllib.parse.quote(cover_filename)}" cover_url = f"{self.base_url}/_audio/{urllib.parse.quote(cover_filename)}"