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:
dotnet new console -n Sora2VideoCLI
cd Sora2VideoCLI
dotnet add package Spectre.Console
PowerShellProjektstruktur
Wir bauen drei zentrale Komponenten:
Program.cs
— Hauptlogik und BenutzerflussConsoleService.cs
— Helfer für Eingabe und formatierte KonsolenausgabeVideoApiClient.cs
— Kommuniziert mit der OpenAI Video-API
Zusätzlich definieren wir zwei kleine DTOs im Ordner Models
:
// 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 erstellenGET /v1/videos/{id}
→ Status abfragenGET /v1/videos/{id}/content
→ Fertiges Video herunterladen
Dazu habe ich VideoApiClient
erstellt, der diese drei Aufrufe kapselt:
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:
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