Exporta a YouTube en .NET C# con bibliotecas de código abierto
Usa el cliente .NET de código abierto de Google para subir un MP4 local a tu canal de YouTube. Esta app de consola solicita acceso solo para subir videos, establece el video como privado, desactiva las notificaciones a suscriptores e imprime el ID del video devuelto por YouTube. La aceptación de la subida y la finalización del procesamiento del video son resultados distintos.
Requisitos previos
Este tutorial usa Bash en Linux y un navegador en la misma computadora. Instala el SDK de .NET 10 con un parche de seguridad vigente. El ejemplo local se verificó con el SDK 10.0.403 y el entorno de ejecución 10.0.14. El SDK incluye el entorno de ejecución; este último por sí solo no permite crear ni compilar un proyecto.
También necesitas un MP4 pequeño y reproducible que te pertenezca o que tengas permiso para subir,
un canal de YouTube y el ID de ese canal. Sigue las
instrucciones de YouTube sobre el ID del canal con la sesión iniciada
como propietario principal del canal de destino. Usa nombres de archivo UTF-8. Esta app envía el
archivo como video/mp4; no convierte otros archivos en video ni valida sus códecs.
Sigue la configuración del proyecto y de OAuth de Google para habilitar YouTube Data API v3 en un proyecto de Google Cloud. Configura la pantalla de consentimiento y agrega tu cuenta de Google como usuario de prueba cuando la app esté en fase de pruebas. Crea un cliente OAuth de tipo Desktop app y descarga su archivo JSON en una ubicación privada fuera del repositorio de código fuente. Una clave de API no autoriza las subidas.
El flujo de OAuth para escritorio de Google usa un navegador y un receptor local de callbacks. Este ejemplo está pensado para una aplicación local interactiva; un servidor remoto sin interfaz gráfica requiere un diseño de autorización independiente. Durante el consentimiento, selecciona el canal de YouTube de destino, incluida la cuenta de marca cuando corresponda. Una cuenta de Google puede tener más de un canal.
Configura el cliente de la API de YouTube
Comienza en una carpeta con permisos de escritura fuera de un proyecto .NET existente. La protección
siguiente rechaza la configuración de proyectos de directorios superiores para que una versión
fijada del SDK o unos ajustes de compilación heredados no puedan cambiar silenciosamente este ejemplo
independiente. El script crea una carpeta nueva youtube-upload y deja la terminal en su
directorio original. Si la carpeta ya existe, se conserva y la configuración se detiene.
(
command -v dotnet >/dev/null || exit 1
parent=$(pwd -P)
while :; do
for name in global.json Directory.Build.props Directory.Build.targets Directory.Packages.props NuGet.Config nuget.config NuGet.config; do
if [ -e "$parent/$name" ]; then
printf 'Use a folder without parent .NET configuration: %s\n' "$parent/$name" >&2
exit 1
fi
done
[ "$parent" = / ] && break
parent=${parent%/*}
parent=${parent:-/}
done
dotnet --version &&
mkdir youtube-upload &&
cd youtube-upload &&
dotnet new console --framework net10.0 &&
dotnet add package Google.Apis.YouTube.v3 --version 1.78.0.4309 &&
dotnet build
)
El paquete es el cliente de YouTube Data API para .NET de Google. Si la creación, la instalación o la compilación fallan, resuelve el error indicado antes de continuar. Conserva los archivos ya creados; no pegues el bloque de creación sobre un proyecto existente.
Sube videos
Reemplaza el archivo youtube-upload/Program.cs generado por este programa completo. El programa usa
GoogleWebAuthorizationBroker y FileDataStore
para obtener y conservar la autorización del usuario. Un directorio de tokens está asociado al canal
que seleccionaste durante el consentimiento; su nombre no selecciona un canal.
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Services;
using Google.Apis.Upload;
using Google.Apis.Util.Store;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
public static class Program
{
public static async Task<int> Main(string[] args)
{
if (args.Length != 5 || string.IsNullOrWhiteSpace(args[3]) ||
string.IsNullOrWhiteSpace(args[4]) || args[4].Length > 100)
{
Console.Error.WriteLine(
"Usage: client.json video.mp4 token-directory expected-channel-id title");
return 2;
}
if (!File.Exists(args[0]) || !File.Exists(args[1]))
{
Console.Error.WriteLine("OAuth client file or video is missing. No upload started.");
return 2;
}
try
{
using var clientFile = File.OpenRead(args[0]);
using var videoFile = File.OpenRead(args[1]);
using var deadline = new CancellationTokenSource(TimeSpan.FromMinutes(10));
var store = new FileDataStore(Path.GetFullPath(args[2]), true);
var cached = await store.GetAsync<TokenResponse>("channel-owner");
Console.Error.WriteLine(cached is null
? "Opening browser authorization. Select the intended YouTube channel."
: "Using saved authorization; no new channel selection was requested.");
var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.FromStream(clientFile).Secrets,
new[] { YouTubeService.Scope.YoutubeUpload },
"channel-owner", deadline.Token, store);
var scopes = credential.Token.Scope?.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (scopes is null || scopes.Length != 1 ||
scopes[0] != YouTubeService.Scope.YoutubeUpload)
{
Console.Error.WriteLine("Expected upload-only consent. Use a fresh token directory.");
return 1;
}
using var youtube = new YouTubeService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "PrivateVideoUpload"
});
youtube.HttpClient.Timeout = TimeSpan.FromSeconds(60);
await UploadPrivateVideoAsync(youtube, videoFile, args[3], args[4], deadline.Token);
Console.WriteLine("Private upload accepted; processing may still be pending.");
return 0;
}
catch (Exception)
{
Console.Error.WriteLine(
"Upload failed or its outcome is unknown. Inspect the selected channel in " +
"YouTube Studio before retrying. Do not automatically repeat this upload.");
return 1;
}
}
public static async Task<string> UploadPrivateVideoAsync(
YouTubeService youtube, Stream source, string expectedChannelId,
string title, CancellationToken cancellationToken)
{
var video = new Video
{
Snippet = new VideoSnippet { Title = title, CategoryId = "22" },
Status = new VideoStatus { PrivacyStatus = "private" }
};
var upload = youtube.Videos.Insert(video, "snippet,status", source, "video/mp4");
upload.NotifySubscribers = false;
Console.Error.WriteLine($"Upload settings: privacy={video.Status.PrivacyStatus}; notifySubscribers={upload.NotifySubscribers}");
upload.ChunkSize = ResumableUpload.MinimumChunkSize;
upload.ProgressChanged += progress =>
{
if (progress.Status == UploadStatus.Uploading)
Console.Error.WriteLine($"Bytes sent: {progress.BytesSent}");
};
var progress = await upload.UploadAsync(cancellationToken);
progress.ThrowOnFailure();
var result = upload.ResponseBody;
if (progress.Status != UploadStatus.Completed || string.IsNullOrWhiteSpace(result?.Id))
throw new InvalidOperationException("No confirmed video ID was returned.");
Console.WriteLine($"Video ID: {result.Id}");
if (result.Snippet?.ChannelId != expectedChannelId || result.Snippet?.Title != title ||
result.Status?.PrivacyStatus != "private")
throw new InvalidOperationException("Returned video metadata did not match the upload.");
return result.Id;
}
}
Desde la carpeta de trabajo original, ejecuta el siguiente comando después de reemplazar los cinco
argumentos. Elige una ruta privada de directorio de tokens que no se haya usado para la primera
autorización. El comando compila el programa guardado antes de ejecutarlo;
-- separa los argumentos de la aplicación de las opciones de .NET.
dotnet run --project ./youtube-upload -- \
"/absolute/path/client.json" "/absolute/path/my video.mp4" \
"/absolute/private/path/youtube-channel-tokens" \
"YOUR_CHANNEL_ID" "My private upload"
Completa el flujo de consentimiento de Google en el navegador. El programa solicita únicamente
youtube.upload, uno de los ámbitos aceptados por
videos.insert. El programa envía explícitamente
privacyStatus=private y notifySubscribers=false, cuyo valor predeterminado sería
true de otro modo.
Cuando YouTube acepta la subida, el programa imprime Video ID: seguido del ID real. Guarda ese ID e inspecciona el video en YouTube Studio en el canal seleccionado. El argumento del ID del canal esperado sirve para verificar los metadatos devueltos después de la subida, no para seleccionar el destino ni para impedir una subida a un canal autorizado por error. Si la verificación falla, el programa termina con un estado de error, pero imprime el ID devuelto para que puedas localizar el video.
Los bytes enviados indican el progreso del transporte. Incluso una subida completada con un ID no
demuestra que YouTube haya terminado de procesar un video reproducible. Verifica el procesamiento
en YouTube Studio antes de publicarlo; Google documenta un
processingDetails.processingStatus independiente para esta etapa.
Gestiona las cuotas y los límites de solicitudes
Consulta los límites actuales de tu proyecto en Google Cloud y la
calculadora de cuotas de YouTube antes de automatizar las subidas.
Al 5 de noviembre de 2026, el valor predeterminado documentado es un cupo independiente de 100 llamadas
a videos.insert al día, además de límites separados para las búsquedas y otros
endpoints. Las cuotas diarias se restablecen a medianoche, hora del Pacífico. Las solicitudes no
válidas también consumen cuota.
Un contador local no puede contabilizar otros procesos, reinicios ni todas las solicitudes del SDK. Este ejemplo realiza un intento de inserción a nivel de aplicación; no implementa un planificador de cuotas para todo el proyecto.
Gestiona los fallos antes de reintentar
Un código de salida distinto de cero puede significar un rechazo, una cancelación, un tiempo de espera agotado o una respuesta perdida después de que YouTube aceptara el video. La app solicita la cancelación de la autorización y la subida después de diez minutos y configura un tiempo de espera HTTP de 60 segundos por solicitud de subida. La cancelación es cooperativa y no garantiza la terminación del proceso; una conexión lenta puede interrumpir una subida que, por lo demás, sea válida. Ninguno de los dos tiempos de espera demuestra que se haya revertido una escritura remota.
El SDK usa solicitudes de subida reanudable, pero este programa no guarda su URI de sesión para
recuperarla después de que termine el proceso. No incluyas todo el comando
dotnet run en un bucle de reintentos: una nueva
videos.insert puede crear un segundo video si se perdió la primera respuesta.
Inspecciona el canal de destino y cualquier ID de video impreso antes de decidir si volver a
intentarlo. La salida de progreso por sí sola no permite determinar un resultado desconocido.
Mantén la autorización vinculada a su canal
FileDataStore conserva los tokens; no es una bóveda de credenciales cifrada.
Restringe el acceso al JSON del cliente y al directorio de tokens, y mantén ambos fuera del control
de versiones. Reutilizar el directorio puede omitir el navegador, por lo que una ejecución correcta
con datos en caché no demuestra que se haya seleccionado de nuevo el canal. Para autorizar otro
canal, elige otra ruta privada de directorio de tokens que no se haya usado y completa el
consentimiento de nuevo.
Ahora tienes un punto de entrada de consola para una sola subida cuyo resultado confirmado es el ID de un video privado. Mantén el video privado hasta que hayas verificado su canal, su contenido y el resultado del procesamiento en YouTube Studio. Para conocer los campos de solicitud disponibles más allá de este ejemplo, consulta la referencia oficial de subida.
