Skip to content

Commit cf567d0

Browse files
benvinegarclaude
andauthored
feat: add YouTube publisher skill and upload CLI (#4)
* feat: add YouTube publisher skill and upload CLI Add scripts/youtube_publish.py, a YouTube Data API v3 CLI with OAuth auth, resumable uploads, thumbnails, SRT captions, playlists, scheduled publishing, status checks, and metadata updates. Uploads default to private and support --dry-run previews. Add the podguy-youtube-publisher skill and /publish-youtube prompt so pi composes titles, descriptions, chapters, and tags from existing analysis artifacts, plus a [youtube] profile section for show defaults and a youtube uv dependency group. Wire the skill into the startup header and add a smoke test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: address code review findings in youtube_publish.py - carry defaultLanguage through videos.update so it is not wiped - validate 5000-char description limit in the update path - print 100% on the final upload chunk - include video_id in playlist/thumbnail/caption failure messages - re-apply 0600 mode after token refresh writes - require seconds in --publish-at timestamps - strip whitespace-only playlist_id profile values - document update --description-file asymmetry and TOML fallback limits Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: address second-pass review findings - make the TOML fallback parser quote-aware when stripping # comments so hashtags in quoted values (e.g. description_footer) survive - only enforce the 5000-char description limit in update when the description is actually being changed - cover the hashtag-footer case in the smoke test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7a10f4c commit cf567d0

13 files changed

Lines changed: 1591 additions & 72 deletions

File tree

AGENTS.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This repository is primarily operated through `pi` for podcast post-production work.
44

5-
Default to the `podguy-post-production` workflow unless the user explicitly asks for something else. Use `podguy-clip-cutter` when the user asks to export or cut social clips.
5+
Default to the `podguy-post-production` workflow unless the user explicitly asks for something else. Use `podguy-clip-cutter` when the user asks to export or cut social clips. Use `podguy-youtube-publisher` when the user asks to upload, schedule, or manage episodes on YouTube.
66

77
## Primary tasks
88

@@ -12,6 +12,7 @@ Default to the `podguy-post-production` workflow unless the user explicitly asks
1212
- give editorial feedback such as cuts, highlights, weak sections, and insert opportunities
1313
- cut selected highlight moments into review exports for TikTok, Reels, YouTube Shorts, trailers, or social posts
1414
- draft publishing assets such as show notes, quote sheets, and proper noun review
15+
- upload finished episodes to YouTube with metadata composed from analysis artifacts
1516

1617
## Podcast profile
1718

@@ -28,6 +29,7 @@ Default to the `podguy-post-production` workflow unless the user explicitly asks
2829
- skip the visual scanner for audio-only inputs
2930
- use transcript evidence and timecodes whenever possible when giving editorial feedback
3031
- treat generated clip media as review exports, not final mastered social edits
32+
- never upload to YouTube without explicit user confirmation of the final metadata; default uploads to private
3133
- use `scripts/download_sample_media.sh` when a real open-license video-podcast sample is helpful; keep downloaded samples under gitignored `dist/`
3234

3335
## Interaction guidance
@@ -55,10 +57,12 @@ For synthetic or test fixture inputs, default to a leaner evaluation mindset:
5557
- Startup extension: `src/podguy-startup.ts`
5658
- Post-production skill: `src/podguy-post-production/SKILL.md`
5759
- Clip-cutter skill: `src/podguy-clip-cutter/SKILL.md`
60+
- YouTube publisher skill: `src/podguy-youtube-publisher/SKILL.md`
5861
- Transcript CLI: `scripts/transcribe_video.py`
5962
- Transcript prep CLI: `scripts/prepare_transcript_analysis.py`
6063
- Scanner CLI: `scripts/scan_podcast.swift`
6164
- Clip cutter CLI: `scripts/cut_clips.py`
65+
- YouTube publisher CLI: `scripts/youtube_publish.py`
6266

6367
## Validation commands
6468

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All notable user-visible changes to this project are documented in this file.
77
### Added
88

99
- Add CI and local maintenance scripts for formatting, linting, typechecking, and smoke tests.
10+
- Add YouTube publishing: `scripts/youtube_publish.py` CLI (OAuth, resumable uploads, thumbnails, captions, playlists, scheduled publishing), the `podguy-youtube-publisher` skill, a `/publish-youtube` prompt shortcut, and a `[youtube]` profile section.
1011

1112
### Changed
1213

README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Pi-first post-production tooling for podcast and video-podcast editors who want
1212
- Scans video episodes for likely interstitials and non-host inserts.
1313
- Generates chapters, clip candidates, cut reports, show notes, quotes, and proper noun checks.
1414
- Cuts selected highlight ranges into review exports for TikTok, Reels, YouTube Shorts, trailers, or social posts.
15+
- Uploads finished episodes to YouTube with metadata composed from analysis artifacts.
1516

1617
Generated transcripts, scans, thumbnails, notes, and clip exports go under gitignored `dist/` by default.
1718

@@ -157,6 +158,29 @@ uv run python scripts/cut_clips.py \
157158

158159
The cutter writes generated media plus `manifest.json`. Vertical and square modes use center-crop framing, so treat them as review exports unless the framing has been checked.
159160

161+
### Publish to YouTube
162+
163+
One-time setup: create a Google Cloud project with the YouTube Data API v3 enabled, create a Desktop-app OAuth client, save the JSON to `~/.config/podguy/youtube/client_secret.json`, then authenticate:
164+
165+
```bash
166+
uv sync --group youtube
167+
uv run --group youtube python scripts/youtube_publish.py auth
168+
```
169+
170+
Upload an episode (private by default; use `--dry-run` first to preview the request):
171+
172+
```bash
173+
uv run --group youtube python scripts/youtube_publish.py upload \
174+
"episode-006-final.mp4" \
175+
--title "Ep 6: Why this market flipped" \
176+
--description-file dist/analysis/ep006/youtube-description.md \
177+
--chapters-file dist/analysis/ep006/chapters.md
178+
```
179+
180+
Other subcommands cover thumbnails, SRT captions, playlists, scheduled publishing (`--publish-at`), status checks, and metadata updates. Defaults like privacy, category, tags, and a description footer come from the `[youtube]` section of `podguy.toml`.
181+
182+
Each upload costs 1600 of the default 10000 daily YouTube API quota units, and videos uploaded through unverified API projects may stay locked private until the project passes a YouTube API audit.
183+
160184
### Download real sample media
161185

162186
Use the Cordkillers open-license video-podcast excerpt for local evaluation:
@@ -194,9 +218,10 @@ preferred_review = "quick pass"
194218
- [`podguy`](podguy): launcher for pi with repo-local skills, prompts, and startup extension.
195219
- [`src/podguy-post-production/SKILL.md`](src/podguy-post-production/SKILL.md): main editorial workflow skill.
196220
- [`src/podguy-clip-cutter/SKILL.md`](src/podguy-clip-cutter/SKILL.md): social clip export workflow skill.
221+
- [`src/podguy-youtube-publisher/SKILL.md`](src/podguy-youtube-publisher/SKILL.md): YouTube upload workflow skill.
197222
- [`src/podguy-startup.ts`](src/podguy-startup.ts): pi startup widget.
198223
- [`prompts/`](prompts): optional prompt shortcuts.
199-
- [`scripts/`](scripts): deterministic scanner, transcript, prep, fixture, sample, and clip-cutting tools.
224+
- [`scripts/`](scripts): deterministic scanner, transcript, prep, fixture, sample, clip-cutting, and YouTube publishing tools.
200225
- [`tests/`](tests): smoke tests wrapped by Vitest.
201226
- [`AGENTS.md`](AGENTS.md): repo guidance for coding agents.
202227

podguy.example.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,12 @@ preferred_model = ""
2424
[video_scan]
2525
enabled = true
2626
sample_interval_seconds = 0.5
27+
28+
[youtube]
29+
# Defaults for scripts/youtube_publish.py; flags override these.
30+
default_privacy = "private"
31+
default_category = "22"
32+
default_tags = []
33+
playlist_id = ""
34+
description_footer = ""
35+
made_for_kids = false

prompts/publish-youtube.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
description: Upload an episode video to YouTube with metadata drafted from analysis artifacts
3+
---
4+
5+
Publish to YouTube: $@
6+
7+
Workflow:
8+
9+
1. Identify the episode slug and the final episode video file (a real export, not a review cut).
10+
2. Check auth: if `~/.config/podguy/youtube/token.json` is missing, walk through the one-time setup in the `podguy-youtube-publisher` skill before anything else.
11+
3. Draft metadata from existing artifacts and the `[youtube]` section of `podguy.toml`:
12+
- Title (max 100 characters) from show notes or the user's wording.
13+
- Description body written to `dist/analysis/<slug>/youtube-description.md`.
14+
- Chapters from `dist/analysis/<slug>/chapters.md` when present (first chapter must start at `00:00`).
15+
- Tags from proper-noun review or profile `default_tags`.
16+
4. Show the user the drafted metadata and confirm privacy/timing (private default, unlisted, public, or `--publish-at`).
17+
5. Preview the request with `--dry-run`, then upload:
18+
`uv run --group youtube python scripts/youtube_publish.py upload <video> --title "<title>" --description-file dist/analysis/<slug>/youtube-description.md --chapters-file dist/analysis/<slug>/chapters.md`
19+
6. Add `--thumbnail`, `--caption <transcript.srt>`, `--playlist-id`, or `--publish-at` as requested.
20+
7. Confirm with `status <video-id>` and report the YouTube Studio link.
21+
22+
Output expectations:
23+
24+
- Never upload without the user confirming the final metadata.
25+
- State the resulting privacy status plainly (private/scheduled/public) and link YouTube Studio.
26+
- Surface quota or processing errors directly instead of retrying silently.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ dependencies = []
99
transcribe-mlx = ["mlx-whisper>=0.4.3"]
1010
transcribe-faster = ["faster-whisper"]
1111
transcribe-whisper = ["openai-whisper"]
12+
youtube = ["google-api-python-client>=2.100", "google-auth-oauthlib>=1.0"]
1213

1314
[tool.uv]
1415
package = false

scripts/test.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ bash tests/test_transcribe_video.sh
88
bash tests/test_prepare_transcript_analysis.sh
99
bash tests/test_scan_podcast.sh
1010
bash tests/test_cut_clips.sh
11+
bash tests/test_youtube_publish.sh
1112
bash tests/test_download_sample_media.sh
1213
bash tests/test_launcher.sh

0 commit comments

Comments
 (0)