Skip to content
Content
Command

/publish

Publish a finished video to YouTube

From plugin
claude-code-video-toolkit
1.9k13 skills13 commands
Install
$ npx -y skills add digitalsamba/claude-code-video-toolkit --agent claude-code

How it fires

How this command gets triggered: by you, by Claude, or both.

  • Fires itselfClaude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/publish

Context preview

What this command does when you run it.

Publish a finished video to YouTube

Command definition

publish.md
description: Publish a finished video to YouTube

Publish to YouTube

Upload a rendered project to YouTube, auto-filling the metadata from `project.json`. Wraps `tools/youtube_upload.py` (OAuth 2.0 + Data API v3, resumable upload).

project.json + rendered MP4 → metadata draft → dry-run → upload → write back videoId/URL

> **One-time setup required.** YouTube uploads need OAuth (not an API key). If the user > hasn't set this up, point them at `docs/youtube-upload.md` and stop until > `YOUTUBE_CLIENT_SECRETS_FILE` is in `.env` and `python3 tools/youtube_upload.py --auth` > has been run once. Don't attempt an upload without a cached token.

Entry Point

Step 1: Locate the project and its rendered video

If the user named a project, use it. Otherwise scan for candidates:

cd /path/to/claude-code-video-toolkit && ls projects/*/project.json

Read the chosen `project.json` **defensively** — real files carry `render`, `format`, and `publish` blocks beyond the `lib/project/types.ts` schema. Resolve the video file: 1. `render.file` if present (e.g. `out/ai-agent-short.mp4`), relative to the project dir. 2. Else scan the project's `out/*.mp4` and pick the most recent. 3. Confirm the resolved path exists and is non-empty before continuing.

If the project's `phase` isn't `complete`, warn the user and confirm they still want to publish.

Step 2: Assemble metadata (into a `publish` block)

Build a `publish` object and write it back into `project.json` so it's reviewable, editable, and re-runnable. If a `publish` block already exists, use it as defaults.

| Field | How to derive | |-------|---------------| | `title` | Existing `publish.title`, else the hook/title scene's `title`, else the project `name` (humanized). Keep ≤100 chars. | | `description` | Existing `publish.description`, else auto-draft: a 1–2 line summary from the scene narration/titles + a channel footer (links, hashtags). Keep ≤5000 chars. | | `tags` | Existing `publish.tags`, else derive 5–12 topical tags from scene titles + the brand. Comma-joined when passed to the tool. | | `category` | Default `"22"` (People & Blogs). Override per channel/topic — e.g. `"28"` (Science & Tech), `"27"` (Education), `"24"` (Entertainment). | | `thumbnail` | Look for `out/thumbnail.*` or `public/thumbnail.*`. If none and the user wants one, offer to generate via `tools/ideogram4.py` (see the `ideogram4` skill). | | `privacy` | Default **`private`** (safe — the video uploads but stays hidden until you flip it). Offer `unlisted`, `public`, or a scheduled go-live (`--publish-at`, e.g. next morning ~09:00 local in UTC) if the user asks. | | `playlist` | Optional; only if the user has one. |

**Show the assembled metadata to the user and let them edit before uploading.**

Step 3: Dry-run first (no upload)

cd /path/to/claude-code-video-toolkit && python3 tools/youtube_upload.py \
  --video "projects/NAME/out/video.mp4" \
  --title "TITLE" \
  --description-file "projects/NAME/.publish-description.txt" \
  --tags "tag1,tag2,tag3" \
  --category "28" \
  --publish-at "2026-06-10T09:00:00Z" \
  --thumbnail "projects/NAME/out/thumbnail.png" \
  --dry-run --json-out

Write the description to a temp file (`--description-file`) rather than passing a long `--description` on the command line. Parse the JSON: confirm `requestBody` looks right and `authOk` is `true`. If `authOk` is `false`, surface the auth error and have the user run `python3 tools/youtube_upload.py --auth` first — do not proceed.

Step 4: Upload

Re-run the same command **without** `--dry-run`, keeping `--json-out`. Parse the result.

Step 5: Write back and report

On `success`, merge into the project's `publish` block:

"publish": {
  "platform": "youtube",
  "videoId": "<id>",
  "url": "https://www.youtube.com/watch?v=<id>",
  "privacyStatus": "<actual returned status>",
  "publishAt": "<scheduled time or null>",
  "uploadedAt": "<today ISO date>"
}

Append a `sessions[]` entry summarizing the upload, then report to the user:

Published to YouTube

Title:    <title>
URL:      https://www.youtube.com/watch?v=<id>
Privacy:  <actual>  (requested: <requested>)
Schedule: <publishAt or "—">

**If `privacyStatus` came back `private` but you requested public/scheduled**, tell the user plainly: this is the unverified-app lock — the video uploaded but won't go public until their Google Cloud OAuth app is verified. They can publish manually in YouTube Studio.

---

Quick Mode

Direct invocation for experienced users:

/publish ai-agent-short
/publish ai-agent-short --privacy unlisted

Parse the project name and any privacy/schedule overrides, still show the metadata and run a dry-run before the real upload.

---

Tool Reference (`tools/youtube_upload.py`)

| Option | Description | |--------|-------------| | `--video, --input` | Path to the video file | | `--title` | Title (≤100 chars) | | `--description` / `--description-file` | Description text, or a file (`-` = stdin) | | `--tags` | Comma-separated tags (combined ≤500 chars) | | `--category` | Numeric category ID string (default `22`; `28` = Science & Tech) | | `--privacy` | `private` (default) / `unlisted` / `public` | | `--publish-at` | ISO8601 UTC schedule, e.g. `2026-06-10T09:00:00Z` (forces private at insert) | | `--thumbnail` | Custom thumbnail (≤2MB, 1280×720) | | `--captions` + `--captions-language` | Caption file + language code | | `--playlist` | Playlist ID | | `--account` | Channel name namespacing the cached token (default `default`) | | `--auth` | Interactive login only — cache a token and exit | | `--dry-run` | Validate + print the request body without uploading | | `--json-out` | Single machine-readable JSON line on stdout |

---

Quota & Limits (worth knowing)

  • Default API quota is **10,000 units/day**; each upload costs **~1,600 units → ~6 uploads/day**.
  • Hitting quota returns HTTP 403 `quotaExceeded` (the tool reports `errorType: "quota"`
Read more
Ships withclaude-code-video-toolkit

Tell Claude Code what video you want — it writes the script, generates the voiceover, music, and visuals, and renders the MP4.

Get the whole plugin