Create or update a permanent talk page or archived external talk link. Use whenever adding a talk detail page, locally hosting a talk deck, publishing a talk transcript or captions, or preserving an external talk page.
Install
npx skillscat add palewire/palewi-re/publish-talk Install via the SkillsCat registry.
Publish a talk page
Use this workflow to make a selected talks.yaml entry into a permanent page
at /talks/<slug>/. It builds on the reusable talk-detail support already in
the site. Keep the work focused on one talk at a time.
Before publishing
- Treat a user request to publish talk assets as confirmation that they have
permission to republish the deck and recording. Ask only if they say the
permission status is uncertain or restricted. - Keep the original
slides_url,video_url, oraudio_urlintalks.yaml
for source reference. - Use
short_titlewhen the full title is too long for the page heading.
The page displays the remaining title as its subtitle. - Do not deploy the Worker or alter Cloudflare routes as part of this
workflow.
Link an external-only talk
When no approved deck, recording, or other local asset is available, keep the
talk as a catalog entry rather than creating an empty detail page:
- Keep the external event or materials URL in
slides_urlorvideo_url. - Find or create a Wayback Machine snapshot and add it as
archive_url. - Do not set
slugor add local talk assets.
The talk title will link to the archived copy. Any separate source-material
link remains in the parenthetical links.
Link a related guide
When a first-party guide is the useful companion to a talk, add its URL asguide_url. Do not add slug, local assets, or a Wayback snapshot unless
other talk materials also require them. The talks list labels this link
with the talk title.
Create the page assets
Create a directory named for the talk slug:
coltrane/static/talks/<slug>/Commit these source assets to the repository:
slides/slide-01.png, etc. — presentation pages rendered at 150 DPI.index.htmlanddeck.js— a local Reveal.js deck. Match the deck frame
to the presentation's actual aspect ratio.- A compact presentation PDF, kept below the repository's 5 MB file limit.
notes.txt— extracted slide text.transcript.txt— the spoken transcript, required when a recording is
available.captions.vtt— WebVTT captions when the source provides them.
Use .github/skills/archive-media/SKILL.md before downloading media for
preservation. Do not commit the recording itself; it belongs in the privatepalewire-talk-media R2 bucket.
Transcribe every recording
Every hosted recording must have a committed timestamped transcript. Download
and commit public captions as captions.vtt when they exist, then make the
transcript from them. When no captions exist, transcribe the recording with
Whisper and commit transcript.txt plus its readable template fragment.
If Whisper fails on an Apple GPU, retry with its CPU backend. Do not omit the
transcript merely because the source has no captions or the first
transcription attempt fails.
Build text readers
Do not use iframes for slide text or transcripts, and do not show hundreds of
lines by default. Instead:
- Create committed template fragments at
coltrane/templates/coltrane/talks/<slug>-notes.htmlandcoltrane/templates/coltrane/talks/<slug>-transcript.html. - Render those fragments inside the detail template's native
<details>
readers. - Put extracted slide text immediately below the deck.
- Put the timestamped transcript immediately below the recording.
- Round displayed transcript timestamps to whole seconds. Use a uniform
fixed-width time column and 500 weight for timecodes.
The raw notes.txt, transcript.txt, and captions.vtt files are the
committed source artifacts. The HTML fragments are their readable page
presentation.
Add talk metadata
Add the optional fields that apply to the matching record incoltrane/content/talks.yaml:
slug: example-talk
short_title: Example talk
deck_url: /static/talks/example-talk/
pdf_url: /static/talks/example-talk/example-talk.pdf
notes_template: coltrane/talks/example-talk-notes.html
notes_text_url: /static/talks/example-talk/notes.txt
transcript_template: coltrane/talks/example-talk-transcript.html
transcript_text_url: /static/talks/example-talk/transcript.txt
local_video_url: /media/talks/example-talk/video.mp4
audio_url: https://podcasts.example.com/episode
audio_download_url: https://cdn.example.com/episode.m4a
local_audio_url: /media/talks/example-talk/audio.mp3
captions_url: /static/talks/example-talk/captions.vtt
poster_url: /media/talks/example-talk/poster.jpgOnly add URLs for assets that exist. The Downloads section should use the
same labels as the page: Slides PDF, Recording video, Recording audio,Extracted slide text, and Timestamped transcript. A locally hosted audio
recording uses the same folded transcript reader as a video recording and
links to audio_url as the original podcast source. When the episode page
and public enclosure differ, record the stable episode page in audio_url
and the publicly downloadable enclosure in audio_download_url.
For an external-only talk, use only the source and Wayback URLs:
slides_url: https://example.org/event
archive_url: https://web.archive.org/web/20260101000000/https://example.org/eventUpload recording media
Load the wrangler skill before using Wrangler. Verify the pinned version:
make check-wranglerUpload the approved full-size original video and optional poster to the
private R2 bucket. Never transcode or downsize a recording to fit Wrangler's
upload limit. Keep the source file's browser-compatible extension inlocal_video_url; the detail page selects video/mp4 or video/webm from
that extension. Upload the matching content type:
cd workers/static-site
npm exec -- wrangler r2 object put \
palewire-talk-media/talks/<slug>/video.webm \
--remote --file /absolute/path/to/video.webm \
--content-type video/webmWrangler only accepts files up to 300 MiB. Before uploading a larger
recording, confirm that R2_ENDPOINT_URL, R2_ACCOUNT_ID,R2_ACCESS_KEY_ID, and R2_SECRET_ACCESS_KEY are available from the
configured credential file. If they are not available, ask the user where the
credentials are stored; do not assume they are in .env. Use boto3's
multipart upload support, then verify the stored size and content type:
uv run --env-file /path/provided-by-the-user/r2.env python - <<'PY'
import os
from pathlib import Path
import boto3
from boto3.s3.transfer import TransferConfig
path = Path("/absolute/path/to/video.mp4")
bucket = "palewire-talk-media"
key = "talks/<slug>/video.mp4"
client = boto3.client(
"s3",
endpoint_url=os.environ.get("R2_ENDPOINT_URL")
or f"https://{os.environ['R2_ACCOUNT_ID']}.r2.cloudflarestorage.com",
aws_access_key_id=os.environ["R2_ACCESS_KEY_ID"],
aws_secret_access_key=os.environ["R2_SECRET_ACCESS_KEY"],
region_name="auto",
)
client.upload_file(
str(path),
bucket,
key,
ExtraArgs={"ContentType": "video/mp4"},
Config=TransferConfig(
multipart_threshold=8 * 1024 * 1024,
multipart_chunksize=16 * 1024 * 1024,
),
)
metadata = client.head_object(Bucket=bucket, Key=key)
assert metadata["ContentLength"] == path.stat().st_size
assert metadata["ContentType"] == "video/mp4"
PYThe static-site Worker serves media from /media/talks/<slug>/, includingvideo.mp4, video.webm, audio.mp3, and audio.m4a, with byte-range
support. Keep captions in the repository's static assets so they are
committed and baked with the site.
For a podcast, run the preservation backup first with an archive root outside
the repository, verify it, then upload the approved MP3 to the talk-media
bucket:
ARCHIVE_ROOT=/absolute/path/outside/the/repo make media-archive-backup
ARCHIVE_ROOT=/absolute/path/outside/the/repo make media-archive-verify
cd workers/static-site
npm exec -- wrangler r2 object put \
palewire-talk-media/talks/<slug>/audio.m4a \
--remote --file /absolute/path/to/audio.m4a \
--content-type audio/mp4Do not commit the MP3. Commit transcript.txt, captions.vtt, and the
readable transcript fragment instead.
Preview and validate
make serve cannot serve R2 media. For a complete local page with video,
build the site and run the Worker preview:
make bake
cd workers/static-site
npm exec -- wrangler dev --port 8798Open:
http://127.0.0.1:8798/talks/<slug>/Check that the deck fills its frame, the video plays, captions load, text
readers expand without clipping, and Downloads includes every available
source asset. Then run:
make check
make static-worker-validate