- HEIC→JPEG, MOV/AVI→MP4 Konvertierung bei allen Upload-Endpoints (media_utils.py) - ffmpeg im Docker-Image, Video-Thumbnails (extract_video_thumb, poster-Attribut) - Google Analytics entfernt, Umami self-hosted eingebunden (index.html, datenschutz.js) - Admin-Panel Analytics-Tab: Stat-Cards, Sparkline 7 Tage, Top-Seiten (Umami-API-Proxy) - Admin-Panel Tab-Icons korrigiert (aus vorhandenem Phosphor-Sprite) - users.real_name Spalte: Username öffentlich, echter Name privat und optional - Registrierung: Label "Benutzername", Leerzeichen verboten, Profanity-Blockliste - Datenschutzerklärung: GA-Abschnitt durch Umami-Text ersetzt
101 lines
3.3 KiB
Python
101 lines
3.3 KiB
Python
"""
|
|
Einmalige Migration: MOV/AVI/M4V → MP4, HEIC/HEIF → JPEG
|
|
Alle betroffenen Dateien auf Disk konvertieren + DB-URLs aktualisieren.
|
|
|
|
Ausführen im Container:
|
|
python3 /app/migrate_media.py
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
from database import db
|
|
from media_utils import convert_media
|
|
|
|
MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")
|
|
CONVERT_EXTS = {".mov", ".avi", ".m4v", ".heic", ".heif"}
|
|
|
|
|
|
def _new_ext(ext: str) -> str:
|
|
return ".mp4" if ext in {".mov", ".avi", ".m4v"} else ".jpg"
|
|
|
|
|
|
def convert_file(path: str) -> str | None:
|
|
"""Convert file in-place, return new path or None on failure."""
|
|
ext = os.path.splitext(path)[1].lower()
|
|
if ext not in CONVERT_EXTS:
|
|
return None
|
|
print(f" Konvertiere: {path}")
|
|
with open(path, "rb") as f:
|
|
data = f.read()
|
|
converted, new_ext = convert_media(data, os.path.basename(path))
|
|
if new_ext == ext:
|
|
print(f" ✗ Konvertierung fehlgeschlagen, übersprungen.")
|
|
return None
|
|
new_path = path[: -len(ext)] + new_ext
|
|
with open(new_path, "wb") as f:
|
|
f.write(converted)
|
|
os.unlink(path)
|
|
print(f" ✓ → {new_path}")
|
|
return new_path
|
|
|
|
|
|
def url_from_path(path: str) -> str:
|
|
rel = os.path.relpath(path, MEDIA_DIR)
|
|
return "/media/" + rel.replace(os.sep, "/")
|
|
|
|
|
|
def update_db(old_url: str, new_url: str) -> int:
|
|
updates = 0
|
|
with db() as conn:
|
|
# diary_media
|
|
r = conn.execute("UPDATE diary_media SET url=? WHERE url=?", (new_url, old_url))
|
|
updates += r.rowcount
|
|
|
|
# JSON-Arrays: forum_threads, forum_posts, routes
|
|
for table, col in [("forum_threads", "foto_urls"),
|
|
("forum_posts", "foto_urls"),
|
|
("routes", "foto_urls")]:
|
|
rows = conn.execute(f"SELECT id, {col} FROM {table} WHERE {col} LIKE ?",
|
|
(f"%{old_url}%",)).fetchall()
|
|
for row in rows:
|
|
urls = json.loads(row[col] or "[]")
|
|
new_urls = [new_url if u == old_url else u for u in urls]
|
|
conn.execute(f"UPDATE {table} SET {col}=? WHERE id=?",
|
|
(json.dumps(new_urls), row["id"]))
|
|
updates += 1
|
|
|
|
# Einzelne URL-Felder
|
|
for table, col in [("lost_dogs", "foto_url"), ("poison", "foto_url")]:
|
|
r = conn.execute(f"UPDATE {table} SET {col}=? WHERE {col}=?",
|
|
(new_url, old_url))
|
|
updates += r.rowcount
|
|
|
|
return updates
|
|
|
|
|
|
def main():
|
|
total_files = 0
|
|
total_db = 0
|
|
|
|
for root, _, files in os.walk(MEDIA_DIR):
|
|
for fname in files:
|
|
ext = os.path.splitext(fname)[1].lower()
|
|
if ext not in CONVERT_EXTS:
|
|
continue
|
|
old_path = os.path.join(root, fname)
|
|
old_url = url_from_path(old_path)
|
|
new_path = convert_file(old_path)
|
|
if new_path:
|
|
new_url = url_from_path(new_path)
|
|
n = update_db(old_url, new_url)
|
|
print(f" DB: {n} Zeile(n) aktualisiert ({old_url} → {new_url})")
|
|
total_files += 1
|
|
total_db += n
|
|
|
|
print(f"\n✓ Fertig: {total_files} Datei(en) konvertiert, {total_db} DB-Einträge aktualisiert.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|