zoom-lecture-publish
End-to-end automated operation for publishing Zoom recordings as lectures on PORSEO LMS / AI PLAY GUILD, then automatically handing off to note membership article creation. It also supports a branch where Zoom recordings are not uploaded as lectures, but only converted into note articles with eye-catching images and screenshots. Responsible for searching unpublished Zoom recordings, matching with lecture candidates, retrieving VTT transcripts and chat logs, creating summaries/lecture data, generating YouTube-style thumbnails and applying Convex Storage, importing to Mux, publishing to production Convex, notifying Discord forums, handing off to note articles, and deleting incorrectly published videos. Used when requested with commands like "Turn this Zoom video into a lecture", "Find and publish unpublished videos", "Create and link lecture thumbnails", "Notify Discord about the video", "Create a note article after publishing", "Turn this Zoom recording into only a note article", "Don't upload it as a lecture", "Include note thumbnails and screenshots", "Delete this lecture video".
NPX Install
npx skill4agent add nanameru/zoom-lecture-publish-skill zoom-lecture-publishTags
Translated version includes tags in frontmatterSKILL.md Content (Chinese)
View Translation Comparison →zoom-lecture-publish
Things to Follow
- In production operations, always confirm the Convex URL used by the target site. Do not trust the default connections in local or
.vercel/project.json.npx convex - Do not output secrets for Vercel/Convex/Zoom/Discord/Mux. Do not paste them into logs either. If necessary, load them into a temporary file and delete it afterward.
.env - Test with only one video in canary before full publishing. Confirm that and
muxPlaybackIdare visible viathumbnailUrlbefore bulk publishing.getPublishedVideos - Wait for automatic Discord posts, and check active/public archived threads to avoid duplicate posts.
- After completing LMS publishing, use the same materials to hand off to , creating an editable draft, paywall, and Light Plan member-only settings for note. Add a one-time purchase price only if explicitly specified by the user. Do not publish note articles without explicit confirmation from the user.
$note-membership-lecture - If the user requests "Don't upload as a lecture", "Only note", or "Don't publish to LMS", handle it as the note-only branch and skip Convex video creation, Mux import, publishing, and Discord notifications.
- Even in the note-only branch, do not publish note articles without explicit confirmation. Stop at creating an editable draft, note eye-catching image, body screenshots, and preview. The note-only branch defaults to free publishing; add paywall, member-only, or one-time purchase settings only if explicitly specified by the user.
- When deleting incorrectly published content, check not only Convex but also Mux assets and Discord forum threads.
References
- AI PLAY GUILD production runbook
- note membership handoff - Data contract and verification procedures for handing off to note article creation after LMS publishing
Branch Judgment
- LMS Publishing Flow: Purpose includes "Turn into lecture", "Publish", "Upload to LMS", "Notify Discord", "Mux", "Link thumbnail", etc. Use the standard flow below.
- note-only Branch: Purpose includes "Take Zoom recording and create only note article", "Don't upload as a lecture", "Don't publish to LMS", "Create article without video publishing", etc. Use the .
Zoom recording → note-only branch
Zoom Recording → note-only Branch
-
Identify the target recording
- If the user specifies relative dates like "just now", "today", or "yesterday", list candidates with the current date and time zone clearly stated.
- Obtain a Zoom Server-to-Server OAuth token and search cloud recordings for the target period.
- Display only the topic, meeting ID, recording start time, duration, MP4 size, and presence of VTT/Chat for candidates. Do not output download URLs or access tokens.
-
Save materials locally
- By default, save to . If the user requests saving within the repo, use
~/Downloads/zoom-recordings/<YYYY-MM-DD-HHmm-slug>/.note-drafts/assets/<YYYY-MM-DD-slug>/ - Retrieve MP4, TRANSCRIPT/VTT, and CHAT (if available), and save them as ,
recording.mp4,transcript.vtt, andchat.txt.metadata.json - Include only title candidates, meeting ID, Zoom recording file ID, recording start time, duration, saved files, and presence of chat in . Do not include secrets or URLs with tokens.
metadata.json
- By default, save to
-
Create article materials
- Generate a natural Japanese summary, key topics, learning points for readers, and title candidates from the VTT.
- If Zoom chat is available, extract URLs and questions to reflect in the article's reference links and supplements. Remove query parameters, fragments, and authentication information from URLs.
- If necessary, create to compile title/date/sourceRecording/transcriptPath/chatPath/videoPath/summary/links/thumbnailPath/screenshotPaths.
note-drafts/handoffs/zoom-note-only-<YYYY-MM-DD-HHmm>.json
-
Create note eye-catching image and screenshots
- Even in the note-only branch, create a note eye-catching image and body screenshots as a rule. Do not post only the article text.
- Use as the working directory, and save images as
note-drafts/assets/<YYYY-MM-DD-slug>/andeyecatch.png.screenshots/*.jpg - In environments where screenshots can be extracted from MP4, generate candidates at ,
t-45s,t-20s,t,t+20sbased on key points in the VTT. For short recordings, generate candidates based on real-time positions like 5 seconds, 30%, 60%, 85%.t+45s - Prioritize using to generate local MP4 candidates. If
ffmpeg -ss <seconds> -i recording.mp4 -frames:v 1 -q:v 2 screenshots/candidate_<seconds>.jpgis not available, try creating only a representative thumbnail via QuickLook, etc. If that fails, use generated illustrations or official screenshots as alternatives.ffmpeg - Always visually check screenshots and only include those that support nearby explanations in the text. Do not use Zoom participant lists, face-only shots, black bars, unrelated screens, or screens with unreadable text.
- If screenshots do not match UI explanations, do not force them; use screenshots from Zed/official documentation/repositories or conceptual diagrams as body images.
- Resize the eye-catching image to 1280:670 to meet note requirements. If using a recording screenshot, perform center cropping and resizing; if text becomes distorted, create it via AI generation or design images.
- When using a generated eye-catching image, use to create a 1280:670 raster image aligned with the article theme. Prioritize readable titles, margins, and cleanliness for note articles, rather than flashy YouTube-style LMS thumbnails.
$imagegen - Record the created , adopted
eyecatch.png, reasons for rejection, and the heading where the image will be inserted in the handoff.screenshots/*.jpg
-
Handoff to
$note-membership-lecture- Use to create a note article based on the Zoom recording/VTT/Chat/summary.
$note-membership-lecture - Since no LMS URL or Mux playback ID exists, do not force a "Hands-on here" link at the start of the article. Instead, use a natural introduction like "This article organizes key points and procedures based on the recording content" if necessary.
- When creating the note draft, upload and insert adopted screenshots near relevant explanations in the text. Confirm the preview with images before reporting completion.
eyecatch.png - Do not generate LMS thumbnails or apply Convex Storage.
- In the note-only branch, do not include paywall text like "Members-only content below" in the body. Structure the entire article as freely readable.
- In the note-only branch, do not enable Light Plan member access, one-time purchase, , or paywall settings. Switch to the paid setting procedure of
limited=Trueonly if the user explicitly requests paid content.$note-membership-lecture
- Use
-
Report results
- Output the paths of saved MP4/VTT/Chat/metadata, eye-catching image, adopted screenshots, handoff, article key of the draft, preview URL, editor URL, whether it is for free publishing, and whether it is pending note publication.
- Clearly state that Convex/Mux/Discord/LMS publishing was not performed.
Standard Flow
-
Organize inputs
- Confirm the lecture list, existing MD files, thumbnail guidelines, target quantity, and exclusion criteria provided by the user.
- Prioritize existing compilations in , and per-lecture files in
thumbnails/summaries/lectures-for-thumbnails.md.thumbnails/summaries/per-lecture/ - Maintain a correspondence table of lecture titles, recording dates, Zoom meeting IDs, recording times, Mux playback IDs, thumbnail paths, and publishing URLs.
-
Confirm production connection destination
- Retrieve and
NEXT_PUBLIC_CONVEX_URLfrom the Vercel production environment.CONVEX_DEPLOYMENT - Confirm that it matches the Convex URL embedded in the site JS.
- If the CLI points to a different Convex instance, use with
ConvexHttpClientandNEXT_PUBLIC_CONVEX_URLfor operations.CONVEX_INTERNAL_SECRET
- Retrieve
-
Search for Zoom recordings
- Obtain a Zoom Server-to-Server OAuth token and search cloud recordings for the target period by date range.
- Extract ,
MP4/TRANSCRIPT, andVTTfromCHAT.recording_files - Match candidates by recording date, meeting ID, duration, title similarity, and title order in existing MD files.
- Only create a new record without passing to avoid duplicate checks if there is a broken, incomplete record with the same
recordingFileIdin existing Convex. Normally, passzoomRecordingIdto maintain idempotency.recordingFileId
-
Create lecture data
- If VTT is available, read the content to create a lecture overview, learning points, and chapter candidates.
- If Zoom chat is available, save it as supplementary information and reflect questions and URLs in the overview.
- If a per-lecture MD file exists, use it as the source and supplement missing content from the Zoom transcript.
-
Create thumbnails
- Maintain the existing tone if specified. For AI PLAY GUILD lecture thumbnails, base them on YouTube-style large text, strong outlines, and right-side character icon-style faces.
- Do not fix the color to orange every time. Use colors matching the tool or lecture theme: orange for Claude, black/purple/green for Vercel/Slack, white/black/Google colors for Google/Notion, etc.
- Do not make faces look scary. Keep the balance of eyes, mouth, and hairstyle natural, and use clean hairstyles.
- Save generated files to and write the path back to the correspondence table MD file.
thumbnails/generated/lecture-thumbnails/
-
Import to Convex
- Use to convert the Zoom recording into a video record and start Mux import.
api.zoom.createZoomManualImportVideo - Use to refine the title/description/summary.
api.videos.updateVideoMetadataServer - Use to create an upload URL, PUT the PNG, then use
api.videos.generateUploadUrlServerto setapi.videos.setThumbnailServer.customThumbnailStorageId - Since does not show unpublished videos, verify pending Mux imports via a publishing canary or admin query. Do not rely solely on
getPublishedVideosfor waiting judgments, as it may not return Mux IDs.getZoomVideosForMigration
- Use
-
Publish and verify
- Publish only the first video using .
publishVideoServer({ isPublished: true }) - Confirm that the target and
muxPlaybackIdcan be retrieved viathumbnailUrl.getPublishedVideos - If no issues exist, publish the remaining videos and confirm that all have status.
published / mux / thumbnail - Use on the representative URL to confirm HTTP 200. However, since the detail page may return 200 via app-side routing immediately after deletion, make the final judgment via the publishing list query.
curl
- Publish only the first video using
-
Notify Discord
- Let handle posting at publish time, then wait a few seconds.
discordNotify.postVideoToForum - Search active threads and public archived threads in the forum channel, and directly create missing titles via the Bot API.
- Prioritize the summary for the post body; shorten it if too long, and include .
https://aiplayguild.com/videos/<videoId>
- Let
-
Handoff to note articles
- Read to create handoff information for each published video.
references/note-membership-handoff.md - Include title/date/videoId/LMS URL/Mux playback ID/VTT/Zoom chat/summary/thumbnail/shared URL/verification results in the handoff.
- For single lecture publishing, automatically use after verifying LMS publishing and Discord notifications, proceeding to create the note draft, 1280:670 eye-catching image, body images, paywall, and Light Plan member-only settings. Add a one-time purchase price only if explicitly specified by the user.
$note-membership-lecture - For bulk publishing of multiple videos, create note drafts for each lecture unless the user limits the scope. However, wait for explicit confirmation before publishing each note article.
- At the start of the note article, present it as "Hands-on for note membership users" (not a video), paste the thumbnail image, then add a natural link.
- When creating a Light Plan note article from a video published as an LMS lecture from a Zoom recording, always add a prominent bold badge stating "Light Plan Exclusive Article" to the note eye-catching image. Do not use a manual overlay that makes Japanese text look messy; regenerate the thumbnail itself via /image 2 if necessary. Use the same image with the badge for the hands-on thumbnail pasted at the start of the article.
$imagegen
- Read
-
Report results
- Briefly output the quantity, published/unpublished status, Mux ready status, thumbnail ready status, Discord post/skip/error status, and publishing URLs.
- If note articles were created, output the article key, preview URL, editor URL, paywall status, Light Plan status, access model, one-time price (if applicable), and whether it is pending note publication.
- Delete temporary env files and logs containing secrets created during the process.
Incorrect Publication Deletion Flow
- Extract from the URL.
videoId - Confirm the title, Mux asset ID, and Discord post name via .
getPublishedVideos - Search for the Discord forum thread by name in active/public archived threads and delete it.
- Delete the Mux asset via the Mux API if an asset ID exists.
- Delete the Convex record if is available. If not, set it to unpublished via
api.videos.deleteVideoServer, then either deploy a deletion mutation or confirm with the user.publishVideoServer({ isPublished: false }) - Confirm that the target ID no longer appears in .
getPublishedVideos
Related Files
- - Zoom Webhook
src/app/api/webhooks/zoom/route.ts - - Single import
src/app/api/zoom/import/route.ts - - Bulk import
src/app/api/zoom/bulk-import/route.ts - - Zoom recording record creation
convex/zoom.ts - - Mux import, VTT/Chat saving, AI metadata
convex/zoomActions.ts - - Video publishing, thumbnail application, deletion server mutations
convex/videos.ts - - Discord forum posting
convex/discordNotify.ts - - Lecture MD generation for thumbnails
scripts/generate-thumbnail-source-md.mjs - - Bulk thumbnail upload to Convex production
scripts/upload-lecture-thumbnails-to-convex-prod.mjs