KI-Videos mit Sora-2 und .NET erstellen
Lesedauer: 5 Minuten

Mit dem Sora-2-Modell von OpenAI können Entwickler jetzt kurze, KI-generierte Videos direkt aus einem einfachen Text-Prompt erzeugen. In diesem Tutorial bauen wir eine .NET-Konsolenanwendung, die direkt mit dem neuen /videos-Endpunkt von OpenAI interagiert.

Voraussetzungen

Du brauchst:

  • .NET 9 SDK
  • Einen OpenAI API-Schlüssel mit Zugriff auf das Sora-2-Modell
  • Grundlegende Kenntnisse in C#-Async-Programmierung

Projekt erstellen und Abhängigkeit installieren:

PowerShell
dotnet new console -n Sora2VideoCLI
cd Sora2VideoCLI
dotnet add package Spectre.Console
PowerShell

Projektstruktur

Wir bauen drei zentrale Komponenten:

  • Program.cs — Hauptlogik und Benutzerfluss
  • ConsoleService.cs — Helfer für Eingabe und formatierte Konsolenausgabe
  • VideoApiClient.cs — Kommuniziert mit der OpenAI Video-API

Zusätzlich definieren wir zwei kleine DTOs im Ordner Models:

C#
// CreateVideoRequest.cs
public class CreateVideoRequest
{
    public string Prompt { get; set; } = default!;
    public string Model { get; set; } = default!;
    public string Size { get; set; } = default!;
    public string Seconds { get; set; } = default!;
}

// VideoResponse.cs
public class VideoResponse
{
    public string Id { get; set; } = default!;
    public string Status { get; set; } = default!;
    public int Progress { get; set; }
    public string Size { get; set; } = default!;
    public string Seconds { get; set; } = default!;
}
C#

Der Konsolen-Helfer

Um die CLI interaktiv und visuell ansprechend zu gestalten, verwenden wir Spectre.Console.
Die vollständige Implementierung von ConsoleService findest du auf meinem GitHub-Repository.

Kommunikation mit der Sora-2 API

Die OpenAI Video-API arbeitet über drei Endpunkte:

  • POST /v1/videos → Ein neues Video erstellen
  • GET /v1/videos/{id} → Status abfragen
  • GET /v1/videos/{id}/content → Fertiges Video herunterladen

Dazu habe ich VideoApiClient erstellt, der diese drei Aufrufe kapselt:

C#
using Sora2VideoCLI.Models;
using System.Net.Http.Headers;
using System.Text.Json;

namespace Sora2VideoCLI.Services;

public static class VideoApiClient
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
        WriteIndented = false
    };

    public static async Task<VideoResponse?> CreateVideoAsync(
        this HttpClient http, 
        string baseUrl, 
        CreateVideoRequest request, 
        CancellationToken ct = default)
    {
        string url = $"{baseUrl.TrimEnd('/')}/videos";
        using StringContent content = new(JsonSerializer.Serialize(request, JsonOptions));
        content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
        using HttpResponseMessage resp = await http.PostAsync(url, content, ct);
        string json = await resp.Content.ReadAsStringAsync(ct);
        return JsonSerializer.Deserialize<VideoResponse>(json, JsonOptions);
    }

    public static async Task<VideoResponse?> GetVideoStatusAsync(
        this HttpClient http, 
        string baseUrl, 
        string videoId, 
        CancellationToken ct = default)
    {
        string url = $"{baseUrl.TrimEnd('/')}/videos/{videoId}";
        using HttpResponseMessage resp = await http.GetAsync(url, ct);
        string json = await resp.Content.ReadAsStringAsync(ct);
        return JsonSerializer.Deserialize<VideoResponse>(json, JsonOptions);
    }

    public static async Task DownloadVideoContentAsync(
        this HttpClient http, 
        string baseUrl, 
        string videoId, 
        string outputPath, 
        CancellationToken ct = default)
    {
        string url = $"{baseUrl.TrimEnd('/')}/videos/{videoId}/content";
        using HttpResponseMessage resp = await http.GetAsync(url, ct);
        await using var stream = await resp.Content.ReadAsStreamAsync(ct);
        await using var file = File.Create(outputPath);
        await stream.CopyToAsync(file, ct);
    }
}
C#

Die Hauptlogik (Minimal-Program.cs)

Hier kommt alles zusammen – dank Top-Level-Statements in C# bleibt der Code schlank:

C#
using Sora2VideoCLI.Models;
using Sora2VideoCLI.Services;
using Spectre.Console;
using System.Net.Http.Headers;

const string BaseUrl = "https://api.openai.com/v1";
string[] Models = ["sora-2", "sora-2-pro"];
string[] Sizes = ["720x1280", "1280x720", "1024x1792", "1792x1024"];
string[] Durations = ["4", "8", "12"];

ConsoleService.ShowHeader();

// API-Key & Eingaben
string apiKey = ConsoleService.GetStringFromConsole(
  "Enter your OpenAI API Key:")!;
string model = ConsoleService.SelectFromOptions(
  [.. Models], "Select the SORA model:");
string size = ConsoleService.SelectFromOptions(
  [.. Sizes], "Select the video size (Width x Height):");
string seconds = ConsoleService.SelectFromOptions(
  [.. Durations], "Select the video duration (in seconds):");
string prompt = ConsoleService.GetStringFromConsole(
  "Enter the prompt for your video:")!;
string outputPath = ConsoleService.GetStringFromConsole(
  "Enter the output file path (e.g. ./output.mp4):")!;

// HttpClient vorbereiten
using HttpClient http = new()
{
    Timeout = TimeSpan.FromMinutes(15)
};
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
http.DefaultRequestHeaders.UserAgent.ParseAdd("Sora2VideoCli/1.0");

// Video erstellen
ConsoleService.WriteMarkup("[grey]Sending request to create video...[/]");
var createRequest = new CreateVideoRequest { 
  Prompt = prompt, Model = model, Size = size, Seconds = seconds };
var created  = await http.CreateVideoAsync(BaseUrl, createRequest);
if (created is null || string.IsNullOrWhiteSpace(created.Id))
{
    ConsoleService.WriteError("Failed to create video.");
    return;
}
ConsoleService.WriteMarkup($"\n[green]Job created:[/] {created.Id}  [yellow]{created.Status}[/]\n");

// Auf Fertigstellung warten
using var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };
var completed = await WaitForCompletionAsync(http, BaseUrl, created.Id, cts.Token);
if (completed?.Status != "completed")
{
    ConsoleService.WriteError("Video generation failed.");
    return;
}

// Herunterladen
await http.DownloadVideoContentAsync(BaseUrl, completed.Id, outputPath, cts.Token);
ConsoleService.WriteMarkup($"\n[green]Done![/] Saved to: [yellow]{outputPath}[/]\n");
ConsoleService.WaitForExit();

static async Task<VideoResponse?> WaitForCompletionAsync(
  HttpClient http, 
  string baseUrl, 
  string videoId, 
  CancellationToken ct)
{
    return await AnsiConsole.Progress()
        .AutoClear(true)
        .HideCompleted(true)
        .Columns([
            new TaskDescriptionColumn(),
            new ProgressBarColumn(),
            new PercentageColumn(),
            new ElapsedTimeColumn(),
            new SpinnerColumn()
        ])
        .StartAsync(async ctx =>
        {
            var task = ctx.AddTask(
              "[white]Waiting for video...[/]", autoStart: true, maxValue: 100);
            var delay = TimeSpan.FromSeconds(2);
            while (!ct.IsCancellationRequested)
            {
                var status = await http.GetVideoStatusAsync(baseUrl, videoId, ct);
                if (status is null) { await Task.Delay(delay, ct); continue; }
                task.Value = Math.Clamp(status.Progress, 0, 100);
                if (status.Status == "completed") return status;
                await Task.Delay(delay, ct);
            }
            ct.ThrowIfCancellationRequested();
            return null;
        });
}
C#

Beispiel-Durchlauf

Der Ablauf sieht folgendermaßen aus:

Der Nutzer gibt seinen OpenAI API-Schlüssel ein.

Anschließend wird das Video-Modell ausgewählt – sora-2 ist deutlich schneller und günstiger im Vergleich zu sora-2-pro.

Danach wählt der Nutzer Größe und Dauer des Videos.

Anschließend wird der kreative Teil eingegeben – eine Beschreibung des Videos.

Schließlich wird der Pfad angegeben, unter dem die generierte Videodatei gespeichert werden soll.

Die Konsolen-App sendet die Anfrage an die OpenAI Video API und wartet, bis die Video-Generierung fertiggestellt ist.

Als Ergebnis erhält der Nutzer eine Videodatei, hier zur Demonstration als komprimiertes GIF dargestellt.

    Zusammenfassung

    Wir haben soeben einen voll funktionsfähigen .NET-Client für die Video-Generierungs-API von OpenAI („Sora-2“) erstellt. Mit wenigen Helferklassen und Spectre.Console als schönes CLI-Frontend kannst du nun:

    • KI-Videos direkt aus Text erzeugen
    • Den Fortschritt in Echtzeit verfolgen
    • Die fertige Videodatei automatisch herunterladen

    Beachte: Der Einsatz des Modells sora-2 ist nicht billig. Du kannst mit etwa $0,10 pro Sekunde bei kleineren Auflösungen rechnen. Bei einem 4-Sekunden-Video sind das also rund $0,40.

    Repository: tsjdev-apps/sora2-video-cli-dotnet

    OpenAI DevDay 2024: Die wichtigsten Neuerungen im Überblick Bereitstellen von Azure Open AI Services mit LLM Deployments mit Bicep Ollama mit neuer UI – Einfacher Zugriff auf lokale KI-Modelle
    View Comments
    There are currently no comments.