# Publishing videos to YouTube

Connect a YouTube channel, and accepted videos upload there when you publish. agh uploads them **unlisted** (or private, if you choose), so you can watch each one on YouTube before anyone else finds it. Making a video public is a separate step that only an admin takes, one video at a time.

## What goes to YouTube

A video is a file request whose path ends in `.mp4`, `.mov`, `.webm` or `.m4v`, such as a [narrated video](/docs/video). Only videos go to YouTube, and once a channel is connected they go there by default. Admins can send video elsewhere under **Where files go**, and the Publish page can send one video elsewhere for a single publish. Everything else publishes as before, to GitHub, Roblox or your bucket.

When the project also publishes to GitHub, the video files stay out of the repository. Each pull request updates `.agh/youtube.json`, which maps asset ids to their videos:

```json
{
  "generatedBy": "agh",
  "videos": {
    "marketing.pitch": {
      "videoId": "dQw4w9WgXcQ",
      "url": "https://youtu.be/dQw4w9WgXcQ",
      "embed": "https://www.youtube.com/embed/dQw4w9WgXcQ"
    }
  }
}
```

## Connect a channel

An admin opens **Settings › Publishing**, then **Set up** and **Connect YouTube** on the YouTube card. Google asks them to sign in and pick a channel: the one they pick is the one agh uploads to, so each project can use its own. agh keeps Google's access in its secret store, never on the project, and can only upload videos and change who can see them.

Under the channel, admins choose:

| Setting | What it does |
| --- | --- |
| Unlisted or Private | Who can see new uploads. Public isn't offered here. |
| Category | YouTube's category for new uploads, Gaming to start with. |
| Language | The language of titles, descriptions and speech. Left empty, each asset's own language. |

**Disconnect** gives up agh's access. Videos already on YouTube stay there.

## Title, description and label

A video's title is its `youtube_title` spec, or else the first sentence of its brief. Its description is its `youtube_description` spec, or else the brief. Its tags are its tag values.

YouTube asks uploaders to label realistic content made or changed with AI as **altered or synthetic**. agh ticks the label for files whose provenance is `ai` or `ai-assisted`, and shows it on each video's row on the Publish page, where you can change it before publishing.

## Publish

Tick the videos under **Ready to publish** and choose **Publish selected**. agh sends each file in parts, so a large video takes several requests: keep the Publish page open and it carries on, or publish again later. Agents carry on with `publish` or `check_publishing`.

Under **On YouTube**, each video shows where it stands:

- **Uploading**, with how much YouTube has and **Cancel**.
- **YouTube is processing it**: YouTube has the whole file. The link works once processing is done.
- **Published**, with its link and **Copy link**.
- **Failed**, with YouTube's reason and **Retry now**. The video stays accepted.
- **Replaced**: YouTube can't swap a video's file, so when a new version goes up, agh sets the older video to private.

## Make a video public

Once you've watched it, an admin chooses **Make public…** on its row, checks the altered or synthetic label, and confirms. agh never makes a video public on its own, and agents can't.

## Google's audit lock

Google keeps uploads private from apps it hasn't audited yet. If YouTube keeps a video private that agh asked to be unlisted, agh says so in the settings and on the Publish page, and **Make public…** waits. Until the audit, share a private video by inviting viewers in YouTube Studio.

YouTube also limits how many videos one app can upload each day. When it's reached, the video's row says so; retry the next day.

## For operators

agh signs admins in with one Google OAuth client for the whole server. Create a Google Cloud project with the YouTube Data API v3 on, make an OAuth client of type **Web application**, and add `https://<your agh address>/youtube/callback` as its redirect URI. Then keep its id and secret in AWS Secrets Manager as `agh/project-secrets/google-oauth`:

```json
{ "clientId": "1234-abc.apps.googleusercontent.com", "clientSecret": "GOCSPX-..." }
```

A local `agh serve` reads `AGH_GOOGLE_CLIENT_ID` and `AGH_GOOGLE_CLIENT_SECRET` instead. While the Google Cloud project is in testing, add each admin's Google account as a test user; their sign-in lasts 7 days, after which the Publish page asks them to connect again.
