title logo
title right side
12.04.2026 03:18:11 Lokal:
Serverstatus: (6.46/7.16/6.9725.32/1487.1 GByte  
Aussentemperatur : 12.3 °C  Luftdruck : 1014.57 hPas/mbar
Impressions: 1 
Visits heute: 174 
Home  

Das Programm nimmt Gestalt an! Elara steht nun kurz vor dem Ziel, aber wie sie bereits vermutet hat, schleichen sich bei echten DVDs oft tückische Fehler ein.

Hier ist das Update für das Programm. Wir integrieren nun die Validierung der Spielzeit, um "Geister-Titel" auszufiltern, und sorgen dafür, dass die index.html korrekt generiert wird.

Kapitel 20: Die Spreu vom Weizen trennen (Sanity Checks)

Die Story: Elara lässt das Programm über eine DVD laufen, die angeblich 99 Titel hat. Nach einer Stunde stellt sie fest, dass 90 davon nur 10 Sekunden lang sind – ein klassischer Kopierschutz-Trick, um Ripper in die Irre zu führen. Sie erkennt: Ihr Programm braucht Urteilsvermögen. Sie schreibt eine Funktion, die die Metadaten vor dem Schreiben in das HTML-Menü prüft. Nur was länger als 2 Minuten ist, gilt als echter Inhalt.


Das vollständige, bereinigte Programm

Ich habe die HTML-Generierung in die main integriert und einen Check eingebaut, der verhindert, dass leere oder ultrakurze Titel deine Galerie verstopfen.

// Compile: gcc dvd2web_final.c -lpthread -o dvd2web_final
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

#define MAX_THREADS 1 
#define MIN_DURATION 120.0 // Titel unter 2 Minuten ignorieren

typedef struct {
    double duration;
    int width;
    int height;
} VideoMeta;

typedef struct {
    int next_title;
    int total_titles;
    char device[256];
    pthread_mutex_t lock;
} TaskQueue;

typedef struct {
    int id;
    TaskQueue* queue;
} WorkerData;

VideoMeta get_metadata(const char* filename) {
    VideoMeta meta = {0, 0, 0};
    char cmd[512], buffer[128];

    sprintf(cmd, "ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 %s", filename);
    FILE* fp = popen(cmd, "r");
    if (fp && fgets(buffer, sizeof(buffer), fp)) {
        meta.duration = atof(buffer);
        pclose(fp);
    }

    sprintf(cmd, "ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of default=noprint_wrappers=1:nokey=1 %s", filename);
    fp = popen(cmd, "r");
    if (fp && fgets(buffer, sizeof(buffer), fp)) {
        sscanf(buffer, "%d", &meta.width);
        if (fgets(buffer, sizeof(buffer), fp)) sscanf(buffer, "%d", &meta.height);
        pclose(fp);
    }
    return meta;
}

void* worker_func(void* arg) {
    WorkerData* w_data = (WorkerData*)arg;
    TaskQueue* q = w_data->queue;

    while (1) {
        int title = -1;
        pthread_mutex_lock(&q->lock);
        if (q->next_title <= q->total_titles) {
            title = q->next_title++;
        }
        pthread_mutex_unlock(&q->lock);

        if (title == -1) break;

        char cmd[2048], mp4_name[256];
        sprintf(mp4_name, "title_%02d.mp4", title);

        printf("[Worker %d] Extrahiere & Konvertiere Titel %d...\n", w_data->id, title);

        // Kombinierter Befehl: vobcopy -> ffmpeg -> rm
        // Wir nutzen eine Subshell, um sicherzustellen, dass die VOBs pro Titel isoliert sind
        sprintf(cmd, "mkdir -p temp_%d && "
                     "vobcopy -n %d -i %s -o ./temp_%d/ 2>/dev/null && "
                     "ffmpeg -i ./temp_%d/*.vob -c:v libx264 -crf 22 -preset fast "
                     "-map 0:v:0 -map 0:a? -c:a aac -movflags +faststart %s -y -loglevel error && "
                     "rm -rf ./temp_%d/", 
                title, title, q->device, title, title, mp4_name, title);

        system(cmd);
    }
    free(w_data);
    return NULL;
}

int main(int argc, char** argv) {
    if (argc < 3) {
        printf("Usage: %s <device> <num_titles>\n", argv[0]);
        return 1;
    }

    TaskQueue q;
    q.next_title = 1;
    q.total_titles = atoi(argv[2]);
    strncpy(q.device, argv[1], 255);
    pthread_mutex_init(&q.lock, NULL);

    // Threads starten
    pthread_t workers[MAX_THREADS];
    for (int i = 0; i < MAX_THREADS; i++) {
        WorkerData* d = malloc(sizeof(WorkerData));
        d->id = i; d->queue = &q;
        pthread_create(&workers[i], NULL, worker_func, d);
    }

    for (int i = 0; i < MAX_THREADS; i++) pthread_join(workers[i], NULL);

    // HTML Generierung am Ende
    FILE* html = fopen("index.html", "w");
    fprintf(html, "<html><head><style>body{background:#1a1a1a;color:#eee;font-family:sans-serif;} .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:20px;padding:20px;} .item{background:#333;padding:15px;border-radius:8px;} video{width:100%%;}</style></head><body>");
    fprintf(html, "<h1>DVD Library</h1><div class='grid'>");

    for (int i = 1; i <= q.total_titles; i++) {
        char path[64];
        sprintf(path, "title_%02d.mp4", i);

        if (access(path, F_OK) == 0) { // Prüfen ob Datei existiert
            VideoMeta m = get_metadata(path);
            if (m.duration >= MIN_DURATION) {
                fprintf(html, "<div class='item'><h3>Titel %d</h3><p>%02d:%02d | %dx%d</p>", 
                        i, (int)m.duration/60, (int)m.duration%60, m.width, m.height);
                fprintf(html, "<video controls preload='none'><source src='%s' type='video/mp4'></video></div>", path);
            } else {
                printf("Überspringe Titel %d (zu kurz: %.1fs)\n", i, m.duration);
            }
        }
    }

    fprintf(html, "</div></body></html>");
    fclose(html);
    pthread_mutex_destroy(&q.lock);
    printf("Archivierung abgeschlossen.\n");
    return 0;
}

Was Elara in diesem Kapitel gelernt hat:

  1. Isolation der Ressourcen: Durch mkdir -p temp_%d hat jeder Titel sein eigenes Verzeichnis. Das ist wichtig, falls du später MAX_THREADS wieder erhöhst – so löscht Thread A nicht versehentlich die VOB-Datei, die Thread B gerade noch liest.
  2. Dateisystem-Interaktion (access): Bevor sie blind HTML-Code schreibt, prüft sie mit access(path, F_OK), ob die Konvertierung überhaupt erfolgreich war.
  3. Filtern von Metadaten: Der MIN_DURATION Check ist die "Intelligenz" des Programms. Er verhindert, dass 2-sekündige Menü-Loops oder Warnhinweise als "Film" in der Galerie landen.

Elaras Finale

Das Programm läuft. Die DVD ist digitalisiert. Aber Elara merkt: Die MP4-Dateien liegen einfach nur im Ordner. Wenn sie diese über das Internet freigeben will, braucht sie einen Server. Thorne hat alle Standard-Ports in ihrem Viertel gesperrt.

Soll Elara im nächsten Kapitel lernen, wie sie einen eigenen Webserver in C schreibt, der auf einem unkonventionellen Port läuft und die Videos per HTTP-Streaming direkt an ihren Browser liefert?