Exportar archivos a YouTube en .NET C# con código abierto
Exportar videos a YouTube de forma programática es esencial para automatizar la gestión del contenido de video. Esta guía usa la biblioteca cliente oficial de Google para .NET, de código abierto, para subir videos a la YouTube Data API desde una aplicación de escritorio o de línea de comandos, con autorización del usuario, informes de progreso y manejo de errores.
Requisitos previos
- .NET 6.0 o posterior
- Un proyecto de Google Cloud con la YouTube Data API habilitada
- Credenciales de OAuth 2.0 del tipo Desktop app (aplicación de escritorio) de Google Cloud
Console (descarga tu archivo
client_secrets.json), con una pantalla de consentimiento de OAuth y un usuario de prueba autorizado si corresponde - Paquete NuGet Google.Apis.YouTube.v3
Configuración del cliente de la YouTube API
Primero, instala el paquete NuGet necesario:
dotnet add package Google.Apis.YouTube.v3 --version 1.76.0.4262
Usa el siguiente método de fábrica para crear una instancia del servicio de YouTube. La primera
autorización abre un navegador para que el propietario del canal pueda conceder el acceso. Las
subidas a YouTube requieren credenciales de OAuth de usuario; las cuentas de servicio no pueden
reemplazar este flujo. El directorio de tokens almacena los tokens de actualización y debe ser
privado para el usuario de la aplicación. Los dos bloques de clase partial que aparecen a
continuación pertenecen al mismo proyecto.
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Upload;
using Google.Apis.YouTube.v3;
using Google.Apis.Util.Store;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
public partial class YouTubeUploader : IDisposable
{
private readonly YouTubeService _youtubeService;
private YouTubeUploader(YouTubeService youtubeService)
{
_youtubeService = youtubeService;
}
public static async Task<YouTubeUploader> CreateAsync(string credentialsPath, string tokenDirectory)
{
using var stream = File.OpenRead(credentialsPath);
var clientSecrets = GoogleClientSecrets.FromStream(stream).Secrets;
var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
clientSecrets,
new[] { YouTubeService.Scope.YoutubeUpload },
"channel-owner",
CancellationToken.None,
new FileDataStore(tokenDirectory, true));
var youtubeService = new YouTubeService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "YOUR_APP_NAME"
});
return new YouTubeUploader(youtubeService);
}
public void Dispose() => _youtubeService.Dispose();
}
Subida de videos
El siguiente método gestiona las subidas de video con seguimiento del progreso y un manejo integral de errores:
using System;
using System.IO;
using System.Threading.Tasks;
using Google.Apis.Upload;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
public partial class YouTubeUploader
{
public async Task<string> UploadVideoAsync(
string filePath,
string title,
string description,
string[] tags,
IProgress<IUploadProgress>? progress = null)
{
var video = new Video
{
Snippet = new VideoSnippet
{
Title = title,
Description = description,
Tags = tags,
CategoryId = "22" // People & Blogs category
},
Status = new VideoStatus
{
PrivacyStatus = "private" // or "public", "unlisted"
}
};
using var fileStream = File.OpenRead(filePath);
var videosInsertRequest = _youtubeService.Videos.Insert(
video,
"snippet,status",
fileStream,
"video/*");
videosInsertRequest.ChunkSize = ResumableUpload.MinimumChunkSize;
if (progress != null)
{
videosInsertRequest.ProgressChanged += progress.Report;
}
try
{
var uploadResponse = await videosInsertRequest.UploadAsync();
uploadResponse.ThrowOnFailure();
if (uploadResponse.Status != UploadStatus.Completed ||
string.IsNullOrEmpty(videosInsertRequest.ResponseBody?.Id))
{
throw new InvalidOperationException("Video upload did not complete.");
}
return videosInsertRequest.ResponseBody.Id;
}
catch (Google.GoogleApiException ex) when (ex.Error != null &&
(ex.Error.Code == 403 || ex.Error.Code == 429 || ex.Error.Code == 503))
{
throw new InvalidOperationException("YouTube rejected the upload. Check channel access, project quota, and service availability.", ex);
}
finally
{
if (progress != null) videosInsertRequest.ProgressChanged -= progress.Report;
}
}
}
Gestión de cuotas y límites de tasa
Consulta los límites de tu proyecto y la documentación actual de cuotas de YouTube
antes de definir un presupuesto local. Las subidas tienen su propio bucket de cuota y las cuotas
diarias se restablecen a medianoche, hora del Pacífico. La siguiente protección limita la
concurrencia y reserva un presupuesto configurable antes de cada operación, incluidos los intentos
fallidos. Comparte una sola instancia por bucket dentro de un proceso. Es una protección en memoria,
por lo que los reinicios, los reintentos del SDK y otras instancias de la aplicación siguen
requiriendo una supervisión centralizada de las cuotas. Pasa el costo actual de la operación como
quotaCost.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class QuotaManager : IDisposable
{
private readonly SemaphoreSlim _uploadSemaphore;
private readonly Dictionary<DateTime, int> _quotaUsage;
private readonly int _dailyLimit;
private readonly TimeZoneInfo _quotaTimeZone = TimeZoneInfo.FindSystemTimeZoneById("America/Los_Angeles");
public QuotaManager(int dailyLimit, int maxConcurrentUploads = 3)
{
if (dailyLimit <= 0 || maxConcurrentUploads <= 0) throw new ArgumentOutOfRangeException();
_dailyLimit = dailyLimit;
_uploadSemaphore = new SemaphoreSlim(maxConcurrentUploads);
_quotaUsage = new Dictionary<DateTime, int>();
}
public async Task<T> ExecuteWithQuotaAsync<T>(
Func<Task<T>> operation,
int quotaCost)
{
if (quotaCost <= 0 || quotaCost > _dailyLimit) throw new ArgumentOutOfRangeException(nameof(quotaCost));
await _uploadSemaphore.WaitAsync();
try
{
lock (_quotaUsage)
{
var today = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, _quotaTimeZone).Date;
if (!_quotaUsage.ContainsKey(today))
{
_quotaUsage.Clear();
_quotaUsage[today] = 0;
}
if (quotaCost > _dailyLimit - _quotaUsage[today])
{
throw new InvalidOperationException("Daily upload budget would be exceeded");
}
_quotaUsage[today] += quotaCost;
}
return await operation();
}
finally
{
_uploadSemaphore.Release();
}
}
public void Dispose() => _uploadSemaphore.Dispose();
}
Buenas prácticas para el uso en producción
- Implementa una lógica de reintentos con retroceso exponencial para manejar errores transitorios como problemas de red o límites de tasa temporales.
- Almacena tus credenciales de forma segura para proteger tus secretos de OAuth 2.0.
- Supervisa el progreso de las subidas para dar retroalimentación y diagnosticar problemas en tiempo real.
- Asegura un manejo de errores robusto que cubra el exceso de cuota, los fallos de autenticación y las restricciones de tamaño de archivo o de red.
Implementa la lógica de reintentos
El SDK se encarga de los reintentos de las subidas reanudables. Usa el método auxiliar que aparece a
continuación solo para operaciones que se pueden repetir de forma segura, como las solicitudes de
lectura. Reintentar una operación videos.insert completa puede crear videos duplicados si la primera
subida se completó correctamente pero su respuesta se perdió.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Google;
public class RetryExamples
{
public async Task<T> RetryWithExponentialBackoff<T>(
Func<Task<T>> operation,
int maxAttempts = 3)
{
if (maxAttempts <= 0) throw new ArgumentOutOfRangeException(nameof(maxAttempts));
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
try
{
return await operation();
}
catch (Exception ex) when (IsTransientException(ex))
{
if (attempt == maxAttempts) throw;
var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt));
await Task.Delay(delay);
}
}
throw new Exception("Retry attempts exhausted");
}
private bool IsTransientException(Exception ex)
{
return ex is HttpRequestException ||
(ex is GoogleApiException gex && (gex.Error?.Code == 429 || gex.Error?.Code == 503));
}
}
Almacena las credenciales de forma segura
Configura YOUTUBE_CREDENTIALS_PATH en el entorno de tu proceso para que apunte al archivo JSON del cliente
de escritorio que descargaste. Mantén ese archivo y el directorio de tokens fuera del control de
versiones, restringe el acceso al sistema de archivos y usa un gestor de secretos administrado para
los despliegues alojados. Este método auxiliar solo localiza el archivo; no cifra las credenciales.
Las aplicaciones web necesitan el flujo de OAuth del lado del servidor de Google en lugar del flujo
de navegador de escritorio que se muestra aquí.
using System;
public class SecureCredentialManager
{
public string GetCredentialsPath()
{
var path = Environment.GetEnvironmentVariable("YOUTUBE_CREDENTIALS_PATH");
if (string.IsNullOrEmpty(path))
{
throw new InvalidOperationException("YOUTUBE_CREDENTIALS_PATH is not configured");
}
return path;
}
}
Supervisa el progreso de las subidas
using System;
using Google.Apis.Upload;
public class UploadProgressHandler : IProgress<IUploadProgress>
{
public void Report(IUploadProgress progress)
{
var status = progress.Status switch
{
UploadStatus.Uploading => $"Uploading: {progress.BytesSent} bytes sent",
UploadStatus.Failed => "Upload failed",
UploadStatus.Completed => "Upload completed",
_ => $"Status: {progress.Status}"
};
Console.WriteLine(status);
}
}
Conclusión
Exportar archivos a YouTube en .NET C# requiere prácticas de autenticación actualizadas, una gestión cuidadosa de las cuotas y un manejo robusto de los errores. Al integrar la biblioteca cliente de Google APIs con la carga asíncrona de credenciales y seguir las buenas prácticas para los despliegues en producción, puedes construir una solución de subida de videos resiliente. Para un enfoque más ágil del manejo de exportaciones de archivos y del procesamiento de video a gran escala, considera usar el servicio de exportación de archivos de Transloadit.
