Integrations

Uploading Videos to YouTube with a Two-Step GPAL Wizard

Complete Program

Here is the overall structure of the workflow, start to finish. Each piece is broken down and explained below.

using GenerallyPositive;

using GenerallyPositive.Browser;

using GenerallyPositive.GPALForm;

using static GenerallyPositive.Enums;


GPAL

.WithInformationHandler(OnGPALInfo)

.WithExceptionHandler(OnGPALException);


// Step 1: collect Google OAuth credentials

GPAL.Form

.WithTitle("YouTube Upload Wizard -- Step 1 of 2: Google Credentials")

.WithWidth(520).WithHeight(310)

.WithFormControl(GPAL.LabelFor("Client ID:"))

.WithFormControl(clientIdInput)

.WithFormControl(GPAL.LabelFor("Client Secret:"))

.WithFormControl(clientSecretInput)

.WithFormControl(GPAL.LabelFor("Refresh Token:"))

.WithFormControl(refreshTokenInput)

.WithFormControl(connectBtn)

.WithFormControl(cancelCredBtn)

.ShowDialog();


if (!credConnected) return;


// Step 2: configure and upload

GPAL.Form

.WithTitle("YouTube Upload Wizard -- Step 2 of 2: Configure and Upload")

.WithWidth(820).WithHeight(600)

.WithFormControl(configTab)

.WithFormControl(GPAL.LabelFor("Video files:"))

.WithFormControl(fileListCtrl)

.WithFormControl(addFilesBtn)

.WithFormControl(removeBtn)

.WithFormControl(GPAL.LabelFor("Title:"))

.WithFormControl(titleInput)

.WithFormControl(GPAL.LabelFor("Description:"))

.WithFormControl(descInput)

.WithFormControl(GPAL.LabelFor("Privacy:"))

.WithFormControl(privacyCombo)

.WithFormControl(GPAL.LabelFor("Premiere:"))

.WithFormControl(premiereCombo)

.WithFormControl(uploadAllBtn)

.WithFormControl(progressTab)

.WithFormControl(logBox)

.WithFormControl(statusStrip)

.WithFormControl(closeBtn)

.ShowDialog();

Building Google OAuth Credentials

GPAL.Credentials.WithService(CredentialServiceType.Google) starts the credential chain. WithServiceKey takes the Client ID and Client Secret joined by a colon, WithScope pins the credential to YouTube upload permissions, and WithRefreshToken supplies the long-lived refresh token from the OAuth 2.0 Playground. Once ToGPALObject() is called the credential object is ready to pass to GPAL.YouTube.

ICredentials credentials = GPAL.Credentials

.WithService(CredentialServiceType.Google)

.WithServiceKey($"{clientId}:{clientSecret}")

.WithScope(OAuthScope.YouTube_Upload)

.WithRefreshToken(refreshToken)

.ToGPALObject();

NOTE

Client ID and Client Secret come from a project in the Google Cloud Console with the YouTube Data API v3 enabled. The refresh token is obtained from the OAuth 2.0 Playground once you authorize the YouTube upload scope.

Uploading a Video File

GPAL.YouTube.WithCredentials sets the active credential, then WithTitle, WithDescription, and WithPrivacy configure the video metadata before upload. Upload((GPALFile)filePath) performs the upload and returns a YouTubeUploadResult that carries the video ID and processing state -- needed later if you want to schedule a premiere.

YouTubeUploadResult result = GPAL.YouTube

.WithCredentials(credentials)

.WithTitle(title)

.WithDescription(description)

.WithPrivacy(YouTubePrivacy.Private)

.Upload((GPALFile)filePath);

Scheduling a Premiere

After upload, YouTube needs time to process the video before a premiere can be scheduled. WaitForProcessing(60) sets how long to wait between status polls, 60 seconds here and never less than 10. WithOutputTo routes the polling log lines to a RichTextBox so the user can see progress. ScheduleAt starts a background thread that polls the video and sets its premiere time from the PremiereTime enum value as soon as YouTube marks it processed. ScheduleAt returns at once; call WaitForCompletion when the workflow has to wait for the scheduling to finish.

GPAL.YouTube

.WithCredentials(credentials)

.WithVideo(result)

.WaitForProcessing(60)

.WithOutputTo(logRichTextBox)

.ScheduleAt(PremiereTime.In15Minutes);

WARNING

YouTube's processing pipeline can take several minutes for longer videos. ScheduleAt keeps polling until every video is processed or has failed, so there is no timeout to raise. A video that fails, is rejected or is deleted is reported and not scheduled. In a console workflow, call WaitForCompletion so the process does not exit while the polling is still running.

Running Uploads on a Background Thread

Uploads can take minutes, so the wizard runs them on a background thread to keep the UI responsive. The thread uploads every file first, then starts a ScheduleAt for each video that needs a premiere. ScheduleAt returns at once and polls on its own thread, so each video gets a small waiter thread that calls WaitForCompletion and signals a CountdownEvent. The last waiter to finish reports that every premiere is scheduled, so the status only says done when it is.

var thread = new Thread(() =>

{

var toSchedule = new List<(YouTubeUploadResult result, PremiereTime premiere)>();


foreach (VideoConfig config in configs)

{

YouTubeUploadResult result = GPAL.YouTube

.WithCredentials(credentials)

.WithTitle(config.Title)

.WithDescription(config.Description)

.WithPrivacy(config.Privacy)

.Upload((GPALFile)config.FilePath);


if (result != null && config.Premiere.HasValue)

toSchedule.Add((result, config.Premiere.Value));

}


if (0 == toSchedule.Count)

statusStrip.Text = "All uploads complete.";

else

{

var countdown = new CountdownEvent(toSchedule.Count);


foreach (var (result, premiere) in toSchedule)

{

IGPALYouTube scheduler = GPAL.YouTube

.WithCredentials(credentials)

.WithVideo(result)

.WaitForProcessing(60)

.WithOutputTo(logRichTextBox);


scheduler.ScheduleAt(premiere); // returns at once; the polling runs on its own thread


var waiter = new Thread(() =>

{

scheduler.WaitForCompletion();

if (countdown.Signal())

statusStrip.Text = "All videos uploaded and premiere times scheduled.";

}) { IsBackground = true };

waiter.Start();

}

}

}) { IsBackground = true };

thread.Start();

NOTE

Setting statusStrip.Text from the background thread is safe -- GPALStatusStrip marshals the update to the UI thread internally. For other controls accessed from a background thread, use Control.InvokeRequired and BeginInvoke.

💬 Ask GPAL