Transcription YouTube en C# avec HttpClient et Records

De dotnet run à une transcription imprimée, il a fallu environ deux secondes sur ma machine, et la plupart de cela était l'aller-retour réseau. Aucun paquet NuGet n'apparaît nulle part dans cet article — obtenir une transcription YouTube en C# c'est une requête GET, et HttpClient plus System.Text.Json sont livrés dans la boîte depuis des années. À la fin, vous aurez une application console complète avec des types record pour la réponse, une Main asynchrone, et une gestion des erreurs qui lit l'objet d'erreur structuré de l'API au lieu de deviner à partir des codes d'état.
Trois records
L'API retourne {"result": {...}} en cas de succès et {"error": {...}} en cas d'échec, donc modélisez l'enveloppe directement :
public record Transcript(
string VideoId,
string Title,
string PubDate,
string Content,
int ContentSize,
bool Truncated);
public record ApiError(
string Code,
string Message,
int Status,
int? RetryAfterSeconds);
public record Envelope(Transcript? Result, ApiError? Error);
Un seul appel Deserialize<Envelope> gère chaque réponse. Quelle que soit la propriété non-null, c'est votre réponse. RetryAfterSeconds est nullable parce que seules les erreurs de limite de taux le contiennent.
Le programme
Cible .NET 8, fonctionne tel quel sur 6 et 7 :
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
public static class Program
{
private static readonly HttpClient Http = new();
private static readonly JsonSerializerOptions Json =
new(JsonSerializerDefaults.Web);
public static async Task Main(string[] args)
{
var videoUrl = args.Length > 0
? args[0]
: "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
var request = new HttpRequestMessage(HttpMethod.Get,
"https://youtube2text.org/api/transcribe" +
$"?url={Uri.EscapeDataString(videoUrl)}&maxChars=40000");
request.Headers.Add("x-api-key",
Environment.GetEnvironmentVariable("YT2TEXT_KEY"));
var response = await Http.SendAsync(request);
var envelope = JsonSerializer.Deserialize<Envelope>(
await response.Content.ReadAsStringAsync(), Json);
if (envelope?.Error is { } err)
{
Console.Error.WriteLine($"{err.Code}: {err.Message}");
if (err.RetryAfterSeconds is int wait)
Console.Error.WriteLine($"Retry after {wait}s");
Environment.Exit(1);
}
var t = envelope!.Result!;
Console.WriteLine($"{t.Title} ({t.ContentSize} chars, truncated: {t.Truncated})");
Console.WriteLine(t.Content[..Math.Min(t.Content.Length, 400)]);
}
}
JsonSerializerDefaults.Web mappe les champs camelCase de l'API sur les propriétés record PascalCase, donc il n'y a pas un seul attribut [JsonPropertyName] en vue. Le paramètre url accepte chaque variante d'URL YouTube plus les identifiants nus de 11 caractères, et l'en-tête peut être Authorization: Bearer yt_... au lieu de x-api-key si cela s'adapte mieux à votre middleware.
À propos de ce maxChars=40000
maxChars limite la longueur de la transcription ; 150000 est à la fois la valeur par défaut et le plafond absolu. Si les sous-titres d'une vidéo dépassent votre limite, Truncated revient à true et vous obtenez les N premiers caractères — donc vérifiez le drapeau avant de supposer que vous avez le texte complet. J'ai défini 40000 dans l'exemple car ces transcriptions se retrouvent généralement dans un prompt LLM, et à environ quatre caractères par jeton, c'est un budget de 10k jetons. Si vos appels de modèle passent par l'API Responses OpenAI, il y a un moyen de sauter l'ensemble du plomberie HTTP via le serveur MCP avec OpenAI ; l'équivalent Anthropic se trouve dans utiliser les transcriptions avec l'API Claude.
Codes d'erreur sur lesquels brancher
ApiError.Code est une chaîne stable : UNAUTHORIZED (401), VALIDATION_ERROR et INVALID_URL (400), VIDEO_NOT_FOUND et TRANSCRIPT_UNAVAILABLE (404), RATE_LIMIT_EXCEEDED (429), YOUTUBE_ERROR (500). Celui qui surprend les gens est TRANSCRIPT_UNAVAILABLE : les transcriptions proviennent des pistes de sous-titres de YouTube, et une vidéo dont le propriétaire a désactivé les sous-titres n'a rien à retourner — aucune politique de nouvelle tentative ne répare cela. Effectuez une correspondance de motif sur le code, traitez-le comme un skip permanent.
Pour une clé de test sans inscription, faites GET https://youtube2text.org/api/demo-key — elle est partagée et limitée à 5 vidéos par mois par IP, donc c'est pour les expériences uniquement. Si Go est aussi dans votre pile, l'même appel en Golang utilise l'astuce d'enveloppe identique.
La référence machine-lisible complète — chaque paramètre, chaque erreur, prête à coller dans un system prompt — est sur youtube2text.org/api.md.