
Agency Meetup Publish
- 64 installs
- 339 repo stars
- Updated August 4, 2026
- glebis/claude-skills
Publish an AGENCY Community meetup recording to YouTube: download from Zoom, add intro/outro, build thumbnail and timecodes, upload, and add to a playlist.
About
An end-to-end pipeline that publishes AGENCY Community meetup recordings to YouTube by downloading the Zoom recording, adding intro/outro, generating a thumbnail and timecoded description, uploading, and adding to a playlist. A developer uses it to process recorded meetups into published videos.
- Downloads Zoom MP4 and VTT, re-encodes intro to match, and generates timecodes
- Uploads to YouTube, sets the thumbnail, and adds to the community playlist
Agency Meetup Publish by the numbers
- 64 all-time installs (skills.sh)
- Ranked #978 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/glebis/claude-skills --skill agency-meetup-publishAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 64 |
|---|---|
| repo stars | ★ 339 |
| Last updated | August 4, 2026 |
| Repository | glebis/claude-skills ↗ |
What it does
Publish an AGENCY Community meetup recording to YouTube: download from Zoom, add intro/outro, build thumbnail and timecodes, upload, and add to a playlist.
Files
AGENCY Meetup Publish
Publish AGENCY Community meetup recordings to YouTube with intro, thumbnail, description, and timecodes.
Prerequisites
~/ai_projects/youtube-uploader/— YouTube upload scripts with OAuth credentials~/ai_projects/my-video/out/agency-swarm-intro.mp4— AGENCY intro animation (6s, 1920x1080)- Zoom OAuth configured at
~/.zoom_credentials/ ffmpegandffprobeinstalled- Playwright or Chrome installed (for thumbnail rendering)
Pipeline Overview
1. Identify meeting → 2. Download from Zoom → 3. Add intro/outro
→ 4. Generate timecodes → 5. Write description → 6. Create thumbnail
→ 7. Upload to YouTube → 8. Set thumbnail → 9. Add to playlistStep 1: Identify the Meeting
Ask the user for:
- Meeting name or Zoom ID — if unknown, list recent recordings:
python3 ~/.claude/skills/zoom/scripts/zoom_meetings.py recordings --start YYYY-MM-DD- Speaker name — for description and thumbnail
- Topic summary — or derive from transcript
Get recording details:
python3 ~/.claude/skills/zoom/scripts/zoom_meetings.py recording MEETING_IDThis returns MP4 download URL, duration, transcript URL, and other files.
Step 2: Download from Zoom
Download MP4 and VTT transcript in parallel:
# Video (run in background — large file)
curl -L -o ~/Brains/brain/YYYYMMDD-meeting-slug.mp4 "DOWNLOAD_URL"
# Transcript
curl -L -o ~/Brains/brain/YYYYMMDD-meeting-slug.vtt "TRANSCRIPT_URL"Naming convention: YYYYMMDD-meeting-slug.mp4 where slug is a kebab-case topic summary.
Step 3: Add Intro (and Outro if Available)
The intro and meeting likely have different specs. Check both:
ffprobe -v quiet -print_format json -show_streams INTRO.mp4
ffprobe -v quiet -print_format json -show_streams MEETING.mp4Key parameters to match: resolution, fps, audio sample rate, audio channels.
Re-encode intro to match meeting
ffmpeg -y -i ~/ai_projects/my-video/out/agency-swarm-intro.mp4 \
-vf "scale=WIDTH:HEIGHT:force_original_aspect_ratio=decrease,pad=WIDTH:HEIGHT:(ow-iw)/2:(oh-ih)/2:black" \
-r FPS -c:v libx264 -preset fast -crf 18 \
-ar SAMPLE_RATE -ac CHANNELS -c:a aac \
/tmp/intro-matched.mp4Trim meeting start
Review the VTT to find where real content begins. Typically trim 1-3 seconds of silence:
ffmpeg -y -ss TRIM_SECONDS -i MEETING.mp4 \
-c:v libx264 -preset fast -crf 18 \
-ar SAMPLE_RATE -ac CHANNELS -c:a aac \
-r FPS -vf "scale=WIDTH:HEIGHT" \
/tmp/meeting-trimmed.mp4This re-encode is necessary for concat compatibility. Run in background — it takes several minutes for long recordings.
Concatenate
echo "file '/tmp/intro-matched.mp4'" > /tmp/concat-list.txt
echo "file '/tmp/meeting-trimmed.mp4'" >> /tmp/concat-list.txt
# If outro exists:
# echo "file '/tmp/outro-matched.mp4'" >> /tmp/concat-list.txt
ffmpeg -y -f concat -safe 0 -i /tmp/concat-list.txt -c copy \
~/ai_projects/my-video/out/YYYYMMDD-meeting-slug-final.mp4Step 4: Generate Timecodes from Transcript
Read the VTT transcript and identify up to 20 topic transitions. Look for:
- Speaker changes (especially when the main speaker starts)
- Topic shifts signaled by phrases like "давайте", "следующий", "перейдем к"
- Q&A segments
- Demo/screen-share moments
- Conclusion/wrap-up
Important: Timecodes must account for the intro offset. Add the intro duration (typically 6s) to all VTT timestamps.
Format: MM:SS Topic description or H:MM:SS for videos over 1 hour.
Step 5: Write Description
Follow the template from previous AGENCY Community uploads. See references/description-template.md for the full template.
Key sections: 1. Title line — AGENCY Community Meetup: [Topic] 2. Summary paragraph — 2-3 sentences about the speaker and topic 3. Bullet points — 5-7 key topics covered (use •) 4. Timecodes — prefixed with ⏱ Таймкоды: 5. Community line — AGENCY Community — сообщество практиков AI-автоматизации. 6. Links — prefixed with 🔗, using → arrows 7. Hashtags — topic-relevant, always include #AGENCY #Community
Save description to /tmp/youtube-description.txt.
Step 6: Create Thumbnail
Use the AGENCY thumbnail HTML template rendered to 1280x720 PNG.
Template approach
Write an HTML file at /tmp/thumbnail.html based on references/thumbnail-template.html. Customize:
- Title text — short, punchy (2-4 words max per line)
- Subtitle — in EB Garamond italic
- Speaker line —
Speaker Name × AGENCY - Speaker photo — if available, use with
mix-blend-mode: hard-light
Render to PNG
Preferred: headless Chrome (no install needed):
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--headless --screenshot=/tmp/thumbnail.png \
--window-size=1280,720 --hide-scrollbars \
"file:///tmp/thumbnail.html"Fallback: Playwright:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={'width': 1280, 'height': 720})
page.goto('file:///tmp/thumbnail.html')
page.wait_for_timeout(2000)
page.screenshot(path='/tmp/thumbnail.png')
browser.close()Step 7: Upload to YouTube
cd ~/ai_projects/youtube-uploader && python3 youtube_upload.py \
--video ~/ai_projects/my-video/out/FINAL.mp4 \
--title "TITLE" \
--description "$(cat /tmp/youtube-description.txt)" \
--category education \
--privacy public \
--tags "TAG1,TAG2,..." \
--yesThe upload script outputs the Video ID and Video URL on success. Capture these for the next steps.
Title guidelines: Keep under 100 chars. Format: Topic — Speaker Name. Include key terms for search.
Step 8: Set Thumbnail
cd ~/ai_projects/youtube-uploader && \
python3 youtube_manage.py thumbnails set VIDEO_ID /tmp/thumbnail.pngStep 9: Add to Playlist
The AGENCY Community playlist ID is PLZNP0SKU2SqjHOy01UjDxhSRtcluvuw0m.
cd ~/ai_projects/youtube-uploader && \
python3 youtube_manage.py playlists add-item PLZNP0SKU2SqjHOy01UjDxhSRtcluvuw0m VIDEO_IDParallelization Strategy
For speed, run these in parallel where possible:
| Phase | Can parallelize with |
|---|---|
| Download MP4 | Download VTT |
| Re-encode meeting (bg) | Read transcript, draft timecodes |
| Upload to YouTube (bg) | Generate thumbnail |
| Set thumbnail | Add to playlist |
Output
Report to the user:
- Video URL:
https://www.youtube.com/watch?v=VIDEO_ID - Studio URL:
https://studio.youtube.com/video/VIDEO_ID/edit - Playlist: AGENCY Community
Existing Skills Used
This pipeline builds on:
- zoom —
~/.claude/skills/zoom/for Zoom recording access - video-youtube-upload —
~/.claude/skills/video-youtube-upload/for upload reference - youtube —
~/.claude/skills/youtube/for post-upload management - agency-socials —
~/.claude/skills/agency-socials/for cover design system reference
YouTube Description Template
Use this template for all AGENCY Community meetup uploads. Fill in the bracketed sections.
AGENCY Community Meetup: [Full Topic Title]
[Speaker Name] ([speaker context — role, project, or website]) [1-2 sentence summary of what they present and why it matters].
В этом видео:
• [Key topic 1]
• [Key topic 2]
• [Key topic 3]
• [Key topic 4]
• [Key topic 5]
• [Key topic 6 — optional]
• [Key topic 7 — optional]
⏱ Таймкоды:
0:00 Вступление [brief note]
[MM:SS] [Topic description]
[MM:SS] [Topic description]
... (max 20 timecodes)
AGENCY Community — сообщество практиков AI-автоматизации.
🔗 Ссылки:
→ [Speaker's project/site]: [URL]
→ [Speaker's Telegram/social]: [URL]
→ AGENCY Community: https://the-agency.community/
→ Skills Library: https://skills.salient.community/
→ Gleb Kalinin: https://glebkalinin.com/
→ Claude Code: https://claude.ai/claude-code
#[TopicHashtag1] #[TopicHashtag2] #AGENCY #Community #AIGuidelines
- Description language matches the meeting language (usually Russian)
- Title line is in English format: "AGENCY Community Meetup: [Topic]"
- Bullet points use • not -
- Timecodes account for intro offset (add ~6s to raw VTT times)
- Links section always includes AGENCY Community, Skills Library, Gleb, Claude Code
- Add speaker-specific links at the top of the links section
- Tags: always include AGENCY, AI, Community; add topic-specific tags
- Hashtags: mix Russian and English, always end with #AGENCY #Community
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1280, height=720">
<title>AGENCY Meetup Thumbnail</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Geist', sans-serif;
background-color: #FF6B1A;
width: 1280px;
height: 720px;
overflow: hidden;
position: relative;
}
.container {
position: relative;
height: 100%;
display: flex;
align-items: center;
}
.content {
position: relative;
z-index: 2;
padding-left: 60px;
max-width: 65%;
}
/* CUSTOMIZE: Main title — short, punchy, 2-4 words per line */
h1 {
font-size: 120px;
font-weight: 900;
color: white;
line-height: 0.88;
letter-spacing: -0.03em;
}
/* CUSTOMIZE: Subtitle in EB Garamond italic */
.subtitle {
font-family: 'EB Garamond', serif;
font-size: 72px;
font-style: italic;
font-weight: 400;
color: white;
margin-left: 180px;
margin-top: -5px;
opacity: 0.95;
}
/* CUSTOMIZE: Speaker × AGENCY */
.speaker {
font-family: 'Geist Mono', monospace;
font-size: 28px;
font-weight: 400;
color: white;
opacity: 0.85;
margin-top: 30px;
letter-spacing: 0.05em;
}
/* Right side: speaker photo or decorative element */
.image-container {
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 45%;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 40px;
}
/* Use for speaker photo if available */
.image-container img {
max-height: 85%;
max-width: 100%;
object-fit: contain;
mix-blend-mode: hard-light;
opacity: 0.7;
}
/* Use for decorative text if no photo */
.decorative {
font-family: 'Geist Mono', monospace;
font-size: 200px;
color: white;
opacity: 0.15;
font-weight: 900;
line-height: 0.85;
text-align: right;
}
.agency-text {
position: absolute;
bottom: 25px;
right: 30px;
font-family: 'Geist Mono', monospace;
font-size: 30px;
font-weight: 500;
color: white;
letter-spacing: 0.3em;
z-index: 10;
}
</style>
</head>
<body>
<div class="container">
<div class="content">
<!-- CUSTOMIZE these three elements -->
<h1>TITLE<br>LINE 2</h1>
<div class="subtitle">subtitle</div>
<div class="speaker">Speaker Name × AGENCY</div>
</div>
<!-- Option A: Speaker photo -->
<!-- <div class="image-container">
<img src="PHOTO_PATH" alt="Speaker">
</div> -->
<!-- Option B: Decorative text (when no photo available) -->
<div class="image-container">
<div class="decorative">?!<br>∅<br>✓</div>
</div>
<div class="agency-text">AGENCY</div>
</div>
</body>
</html>