zoom-lecture-publish
Handles Zoom recordings from PORSEO LMS / AI PLAY GUILD end-to-end, including lecture data, thumbnails, Mux, Convex, Discord, and handoff to note articles.
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 .
- 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.
- Test with only one video in canary before full publishing. Confirm that and are visible via before bulk publishing.
- 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.
- 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
Read the AI PLAY GUILD production-specific secure connection procedures, verification rules, and Discord/Mux deletion procedures only when needed:
- 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
At the start of work, classify the user's intent into one of the following:
- 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
.
If the intent is ambiguous and could lead to external publishing via Convex/Mux/Discord, proceed with the note-only branch up to material collection and draft preparation without performing any publishing operations.
Zoom Recording → note-only Branch
Retrieve a Zoom recording and create only a note article. Do not create LMS video records, Mux assets, Convex Storage thumbnails, or Discord forum posts.
-
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
~/Downloads/zoom-recordings/<YYYY-MM-DD-HHmm-slug>/
. If the user requests saving within the repo, use note-drafts/assets/<YYYY-MM-DD-slug>/
.
- Retrieve MP4, TRANSCRIPT/VTT, and CHAT (if available), and save them as , , , and .
- 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.
-
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
note-drafts/handoffs/zoom-note-only-<YYYY-MM-DD-HHmm>.json
to compile title/date/sourceRecording/transcriptPath/chatPath/videoPath/summary/links/thumbnailPath/screenshotPaths.
-
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
note-drafts/assets/<YYYY-MM-DD-slug>/
as the working directory, and save images as and .
- In environments where screenshots can be extracted from MP4, generate candidates at , , , , based on key points in the VTT. For short recordings, generate candidates based on real-time positions like 5 seconds, 30%, 60%, 85%.
- Prioritize using
ffmpeg -ss <seconds> -i recording.mp4 -frames:v 1 -q:v 2 screenshots/candidate_<seconds>.jpg
to generate local MP4 candidates. If is not available, try creating only a representative thumbnail via QuickLook, etc. If that fails, use generated illustrations or official screenshots as alternatives.
- 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.
- Record the created , adopted , reasons for rejection, and the heading where the image will be inserted in the handoff.
-
- Use to create a note article based on the Zoom recording/VTT/Chat/summary.
- 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.
- 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 only if the user explicitly requests paid content.
-
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
thumbnails/summaries/lectures-for-thumbnails.md
, and per-lecture files in 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 from the Vercel production environment.
- Confirm that it matches the Convex URL embedded in the site JS.
- If the CLI points to a different Convex instance, use with and for operations.
-
Search for Zoom recordings
- Obtain a Zoom Server-to-Server OAuth token and search cloud recordings for the target period by date range.
- Extract , /, and from .
- 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 in existing Convex. Normally, pass to maintain idempotency.
-
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
thumbnails/generated/lecture-thumbnails/
and write the path back to the correspondence table MD file.
-
Import to Convex
- Use
api.zoom.createZoomManualImportVideo
to convert the Zoom recording into a video record and start Mux import.
- Use
api.videos.updateVideoMetadataServer
to refine the title/description/summary.
- Use
api.videos.generateUploadUrlServer
to create an upload URL, PUT the PNG, then use api.videos.setThumbnailServer
to set .
- Since does not show unpublished videos, verify pending Mux imports via a publishing canary or admin query. Do not rely solely on
getZoomVideosForMigration
for waiting judgments, as it may not return Mux IDs.
-
Publish and verify
- Publish only the first video using
publishVideoServer({ isPublished: true })
.
- Confirm that the target and can be retrieved via .
- If no issues exist, publish the remaining videos and confirm that all have
published / mux / thumbnail
status.
- 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.
-
Notify Discord
- Let
discordNotify.postVideoToForum
handle posting at publish time, then wait a few seconds.
- 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>
.
-
Handoff to note articles
- Read
references/note-membership-handoff.md
to create handoff information for each published video.
- 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.
- 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.
-
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.
- Confirm the title, Mux asset ID, and Discord post name via .
- 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
api.videos.deleteVideoServer
is available. If not, set it to unpublished via publishVideoServer({ isPublished: false })
, then either deploy a deletion mutation or confirm with the user.
- Confirm that the target ID no longer appears in .
Related Files
src/app/api/webhooks/zoom/route.ts
- Zoom Webhook
src/app/api/zoom/import/route.ts
- Single import
src/app/api/zoom/bulk-import/route.ts
- Bulk import
- - Zoom recording record creation
- - Mux import, VTT/Chat saving, AI metadata
- - Video publishing, thumbnail application, deletion server mutations
- - Discord forum posting
scripts/generate-thumbnail-source-md.mjs
- Lecture MD generation for thumbnails
scripts/upload-lecture-thumbnails-to-convex-prod.mjs
- Bulk thumbnail upload to Convex production