public static async Task <int> Main( IConsole console, string?twitchUserId = null, string?twitchClientId = null, string?twitchClientSecret = null, string?azureStorageAccountKey = null, string?youTubeUsername = null, string?youTubePassword = null, string?youTubeRecoveryEmail = null) { var configBuilder = new ConfigurationBuilder(); configBuilder.AddEnvironmentVariables(); configBuilder.AddUserSecrets(typeof(Program).Assembly); var config = configBuilder.Build(); var section = config.GetSection(nameof(VideoConverter)); twitchUserId ??= section["TwitchUserId"] ?? throw new InvalidOperationException("No Twitch user id specified"); twitchClientId ??= section["TwitchClientId"] ?? throw new InvalidOperationException("No Twitch client id specified"); twitchClientSecret ??= section["TwitchClientSecret"] ?? throw new InvalidOperationException("No Twitch client secret specified"); var storageAccount = StorageAccount.Get(azureStorageAccountKey, config); var tableClient = storageAccount.CreateCloudTableClient(); var streamVideoTables = tableClient.GetTableReference("streamvideos"); var youtubeSettingsTable = tableClient.GetTableReference("youtubesettings"); TwitchAPI api = new(settings : new ApiSettings() { ClientId = twitchClientId, Secret = twitchClientSecret }); var httpClient = new HttpClient(); var twitchClinet = new Twitch(httpClient); console.Out.WriteLine("Retrieving videos from twitch"); var videoResponse = await api.Helix.Videos.GetVideoAsync(userId : twitchUserId); foreach (TwitchVideo video in videoResponse.Videos) { // Check if video exists in storage VideoRow?row = streamVideoTables.CreateQuery <VideoRow>() .Where(x => x.TwitchVideoId == video.Id) .FirstOrDefault(); if (!string.IsNullOrWhiteSpace(row?.YouTubeVideoId)) { console.Out.WriteLine($"Twitch video {video.Id} already has YouTube id '{row.YouTubeVideoId}'; skipping"); continue; } console.Out.WriteLine($"Downloading '{video.Title}' from {video.CreatedAt} - {video.Id} "); FileInfo?downloadedFilePath = await twitchClinet.DownloadVideoFileAsync(video.Id); if (downloadedFilePath is null) { console.Out.WriteLine($"Failed to download video file"); return(1); } console.Out.WriteLine($"Downloaded video to '{downloadedFilePath}'"); FileInfo?trimmedFilePath = await Ffmpeg.TrimLeadingSilence(downloadedFilePath); if (trimmedFilePath is null) { console.Error.WriteLine($"Failed to trim silence from '{downloadedFilePath}'"); return(1); } console.Out.WriteLine($"Trimmed silence '{trimmedFilePath}'"); var youtubeSection = config.GetSection("YouTube"); BrowserCredential creds = new( youTubeUsername ?? youtubeSection["Username"], youTubePassword ?? youtubeSection["Password"], youTubeRecoveryEmail ?? youtubeSection["RecoveryEmail"]); string youTubeId = await UploadVideoAsync(creds, trimmedFilePath, video); await DeleteFile(trimmedFilePath); if (string.IsNullOrWhiteSpace(youTubeId)) { console.Error.WriteLine($"Failed to upload '{trimmedFilePath}'"); return(1); } console.Out.WriteLine($"Uploaded to YouTube '{youTubeId}'"); DateTime recordingDate = video.GetRecordingDate() ?? DateTime.UtcNow.Date; if (row is null) { row = new VideoRow { PartitionKey = recordingDate.Year.ToString(), }; } row.TwitchVideoId = video.Id; row.TwitchPublishedAt = recordingDate; row.YouTubeVideoId = youTubeId; TableOperation insertOperation = TableOperation.InsertOrMerge(row); // Execute the operation. TableResult _ = await streamVideoTables.ExecuteAsync(insertOperation); break; } return(0); }