Best Picks Labor

Destilliertes Wissen zu Schule und Technik

Lokale Videogalerie auf Knopfdruck

Im Projektunterricht werden an unserer Schule jeweils bei der Ausstellung Videos gezeigt. Wir können nun mit folgendem Python Script eine Homepage generieren welche die Videos in einer Liste darstellt mit Thumbnails. Das Script extrahiert mit ffmpeg zuerst aus der ersten Sekunde ein Frame vom Video. Anschliessend erstellt es eine HTML Seite, welches alle Videos übersichtlich zusammenstellt und anklickbar anzeigt.

Die Videos sollten idealerweise im Voraus mit Handbrake auf eine anständige Grösse codiert werden. Das Script macht dies noch nicht selber, FFmpeg sollte aber theoretisch dazu in der Lage sein.

Das Script benötigt FFmpeg installiert. Idealerweise mit Homebrew. Alternativ kann ffmpeg auch anderweitig installiert werden. Der Pfad muss in der obersten Zeile dann angepasst werden.

Code in Python

import os
import subprocess
import glob

# ==============================================================================
#  SKRIPT-KONFIGURATION
#  Bitte passen Sie die folgenden Pfade an Ihre Umgebung an.
# ==============================================================================

# Der Pfad zur FFmpeg-Ausführungsdatei auf Ihrem System.
# Beispiel für Windows: "C:/ffmpeg/bin/ffmpeg.exe"
# Beispiel für Linux/macOS (oft im PATH): "ffmpeg"
# Beispiel für macOS mit Homebrew: "/opt/homebrew/bin/ffmpeg"
# wo liegt homebrew auf mac os? gib ein im Terminal: "which homebrew""
FFMPEG_EXEC_PATH = "/opt/homebrew/bin/ffmpeg"

# Der Name des Ordners, der Ihre Videodateien enthält.
# Dieser Ordner sollte sich im selben Verzeichnis wie das Skript befinden.
VIDEOS_FOLDER_NAME = "videos"

# Der Name der zu erstellenden HTML-Datei.
OUTPUT_HTML_FILENAME = "video_gallery.html"

# ==============================================================================
#  FUNKTION 1: THUMBNAIL-GENERIERUNG
# ==============================================================================

def generate_thumbnails(video_dir, thumbnail_dir, ffmpeg_path):
    """
    Generiert Thumbnails für Videodateien in einem angegebenen Verzeichnis.
    Überspringt Videos, für die bereits ein Thumbnail existiert.

    Args:
        video_dir (str): Das Verzeichnis, das die Videodateien enthält.
        thumbnail_dir (str): Das Verzeichnis, in dem die Thumbnails gespeichert werden sollen.
        ffmpeg_path (str): Der Pfad zur FFmpeg-Ausführungsdatei.
    """
    print("─" * 60)
    print(f"Schritt 1: Generiere fehlende Thumbnails...")
    print(f"Video-Ordner: {video_dir}")
    print(f"Verwende FFmpeg von: {ffmpeg_path}")
    print("─" * 60)

    # Überprüfen, ob FFmpeg existiert und ausführbar ist
    if not os.path.exists(ffmpeg_path):
        print(f"FEHLER: FFmpeg wurde unter '{ffmpeg_path}' nicht gefunden.")
        print("Bitte passen Sie die Variable 'FFMPEG_EXEC_PATH' im Skript an.")
        return
    if not os.access(ffmpeg_path, os.X_OK):
        print(f"FEHLER: FFmpeg unter '{ffmpeg_path}' ist nicht ausführbar. Überprüfen Sie die Dateiberechtigungen.")
        return

    # Unterstützte Video-Erweiterungen
    video_extensions = ["mp4", "webm", "ogg", "mov", "avi", "mkv", "MTS", "MPG", "VOB"]

    # Sicherstellen, dass das Thumbnail-Verzeichnis existiert
    os.makedirs(thumbnail_dir, exist_ok=True)

    # Videodateien im Verzeichnis suchen
    found_videos = []
    for ext in video_extensions:
        found_videos.extend(glob.glob(os.path.join(video_dir, f"*.{ext}")))

    if not found_videos:
        print(f"Keine unterstützten Videodateien im Verzeichnis '{video_dir}' gefunden.")
        return

    for video_file_path in found_videos:
        filename = os.path.basename(video_file_path)
        filename_without_ext = os.path.splitext(filename)[0]
        thumbnail_name = f"{filename_without_ext}.jpg"
        full_thumbnail_path = os.path.join(thumbnail_dir, thumbnail_name)

        # Prüfen, ob Thumbnail bereits existiert
        if os.path.exists(full_thumbnail_path):
            print(f"Thumbnail für \"{filename}\" existiert bereits. Überspringe.")
        else:
            print(f"Generiere Thumbnail für \"{filename}\" ...")
            command = [
                ffmpeg_path, "-hide_banner", "-ss", "00:00:01",
                "-i", video_file_path, "-vframes", "1",
                "-q:v", "2", full_thumbnail_path
            ]
            try:
                subprocess.run(command, check=True, capture_output=True, text=True)
                print(f"  -> Erfolgreich erstellt: {thumbnail_name}")
            except subprocess.CalledProcessError as e:
                print(f"  -> Fehler beim Erstellen des Thumbnails für \"{filename}\":")
                print(f"     Fehlercode: {e.returncode} | Fehler: {e.stderr.strip()}")
            except Exception as e:
                print(f"  -> Ein unerwarteter Fehler ist aufgetreten: {e}")

    print("\nThumbnail-Verarbeitung abgeschlossen.")

# ==============================================================================
#  FUNKTION 2: HTML-GALERIE-ERSTELLUNG
# ==============================================================================

def generate_video_gallery(video_folder, output_html_file):
    """
    Erstellt eine HTML-Datei, die alle unterstützten Videodateien aus dem
    angegebenen Ordner einbindet, inklusive Thumbnails.

    Args:
        video_folder (str): Der Pfad zum Ordner mit den Videos und Thumbnails.
        output_html_file (str): Der Name der zu generierenden HTML-Datei.
    """
    print("\n" + "─" * 60)
    print(f"Schritt 2: Erstelle HTML-Videogalerie...")
    print(f"Lese aus Ordner: {video_folder}")
    print(f"Schreibe nach: {output_html_file}")
    print("─" * 60)

    # Unterstützte Dateiformate
    supported_video_extensions = ('.mp4', '.webm', '.ogg', '.mov', '.mkv')
    supported_thumbnail_extensions = ('.jpg', '.jpeg', '.png', '.gif')

    video_elements = []

    # Überprüfen, ob der Video-Ordner existiert
    if not os.path.isdir(video_folder):
        print(f"FEHLER: Der Ordner '{video_folder}' wurde nicht gefunden.")
        print("Bitte stellen Sie sicher, dass der Ordner existiert und der Name in 'VIDEOS_FOLDER_NAME' korrekt ist.")
        return

    # Alle Dateien im Video-Ordner durchsuchen
    # Sortieren für eine konsistente Reihenfolge
    for filename in sorted(os.listdir(video_folder)):
        if filename.lower().endswith(supported_video_extensions):
            video_path = os.path.join(VIDEOS_FOLDER_NAME, filename) # Relativer Pfad für HTML
            name_without_ext = os.path.splitext(filename)[0]
            
            # Versuche, ein passendes Thumbnail zu finden
            thumbnail_path = ""
            for ext in supported_thumbnail_extensions:
                potential_thumbnail_filename = name_without_ext + ext
                if os.path.exists(os.path.join(video_folder, potential_thumbnail_filename)):
                    thumbnail_path = os.path.join(VIDEOS_FOLDER_NAME, potential_thumbnail_filename)
                    break
            
            if thumbnail_path:
                video_element = f"""
                <div class="video-thumbnail-wrapper" onclick="openModal('{video_path}', '{name_without_ext}')">
                    <img src="{thumbnail_path}" alt="{filename} Thumbnail" class="thumbnail-image">
                    <div class="thumbnail-caption">{name_without_ext}</div>
                </div>"""
                video_elements.append(video_element)
            else:
                print(f"Warnung: Kein Thumbnail für Video '{filename}' gefunden. Es wird nicht in der Galerie angezeigt.")


    if not video_elements:
        print("Keine Videos mit passenden Thumbnails gefunden, um eine Galerie zu erstellen.")
        return

    # HTML-Grundgerüst
    html_content = f"""
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Videos der Projekte 3. Sek</title>
    <style>
        body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 20px; background-color: #f0f2f5; color: #1c1e21; }}
        h1 {{ text-align: center; color: #333; margin-bottom: 30px; }}
        .gallery-wrapper {{ display: flex; flex-wrap: wrap; gap: 20px; justify-content: center; }}
        .video-thumbnail-wrapper {{ width: 240px; background-color: #fff; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); text-align: center; cursor: pointer; transition: transform 0.2s, box-shadow 0.2s; overflow: hidden; }}
        .video-thumbnail-wrapper:hover {{ transform: translateY(-5px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); }}
        .thumbnail-image {{ display: block; width: 100%; height: 135px; object-fit: cover; }}
        .thumbnail-caption {{ padding: 12px 8px; font-size: 0.9em; color: #555; word-break: break-all; }}
        .modal {{ display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.9); align-items: center; justify-content: center; }}
        .modal-content {{ position: relative; margin: auto; max-width: 90vw; max-height: 90vh; background-color: #000; }}
        .modal-video {{ width: 100%; height: auto; display: block; max-height: 85vh; }}
        .modal-caption {{ color: #ccc; font-size: 1.1em; text-align: center; padding: 15px; word-break: break-all; }}
        .close {{ position: absolute; top: 15px; right: 35px; color: #f1f1f1; font-size: 40px; font-weight: bold; transition: 0.3s; cursor: pointer; }}
        .close:hover, .close:focus {{ color: #bbb; text-decoration: none; }}
    </style>
</head>
<body>
    <h1>Videogalerie</h1>
    <div class="gallery-wrapper">
        {''.join(video_elements)}
    </div>

    <div id="videoModal" class="modal">
        <span class="close" onclick="closeModal()">&times;</span>
        <div class="modal-content">
            <video id="modalVideoPlayer" class="modal-video" controls autoplay>Ihr Browser unterstützt das Video-Tag nicht.</video>
            <div id="modalCaption" class="modal-caption"></div>
        </div>
    </div>

    <script>
        const modal = document.getElementById("videoModal");
        const modalVideo = document.getElementById("modalVideoPlayer");
        const modalCaption = document.getElementById("modalCaption");

        function openModal(videoSrc, captionText) {{
            modal.style.display = "flex";
            modalCaption.textContent = captionText;
            modalVideo.src = videoSrc;
            modalVideo.play();
        }}

        function closeModal() {{
            modal.style.display = "none";
            modalVideo.pause();
            modalVideo.src = ""; // Stop download
        }}

        modal.addEventListener('click', (event) => {{
            if (event.target === modal) {{
                closeModal();
            }}
        }});

        document.addEventListener('keydown', (event) => {{
            if (event.key === 'Escape' && modal.style.display === 'flex') {{
                closeModal();
            }}
        }});
    </script>
</body>
</html>
    """

    # HTML-Datei schreiben
    try:
        with open(output_html_file, "w", encoding="utf-8") as f:
            f.write(html_content)
        print(f"\nDie HTML-Datei '{output_html_file}' wurde erfolgreich erstellt.")
    except Exception as e:
        print(f"Fehler beim Schreiben der HTML-Datei: {e}")

# ==============================================================================
#  HAUPTFUNKTION (main)
# ==============================================================================

if __name__ == "__main__":
    # Basispfad des Skripts ermitteln
    script_dir = os.path.dirname(os.path.abspath(__file__))
    
    # Vollständige Pfade für Ordner und Dateien definieren
    videos_folder_path = os.path.join(script_dir, VIDEOS_FOLDER_NAME)
    output_html_path = os.path.join(script_dir, OUTPUT_HTML_FILENAME)
    
    # 1. Thumbnails generieren (werden im selben Ordner wie die Videos gespeichert)
    generate_thumbnails(videos_folder_path, videos_folder_path, FFMPEG_EXEC_PATH)
    
    # 2. HTML-Galerie erstellen
    generate_video_gallery(videos_folder_path, output_html_path)
    
    print("\n" + "="*60)
    print("Prozess abgeschlossen.")
    print(f"Öffnen Sie die Datei '{OUTPUT_HTML_FILENAME}' in Ihrem Browser, um die Galerie anzusehen.")
    print("="*60)