My personal webpage and resumé
  • Python 39.7%
  • Astro 34.1%
  • TypeScript 25.1%
  • Shell 0.8%
  • CSS 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Morten Olsen 24ea798d40
All checks were successful
ci/woodpecker/push/compliance Pipeline was successful
ci/woodpecker/push/quality Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
content: add new projects
2026-09-03 22:50:31 +02:00
.claude new style 2026-05-02 23:05:07 +02:00
.github/workflows more readme stuff 2026-03-21 16:17:04 +01:00
.vscode rewrite 2025-12-02 23:05:56 +01:00
.woodpecker ci: switch deploy to pages_admin_token (new pages server) 2026-05-05 21:48:01 +02:00
scripts add audio 2026-05-31 12:43:29 +02:00
src content: add new projects 2026-09-03 22:50:31 +02:00
.gitignore add Forgejo + Woodpecker CI alongside GitHub Actions 2026-05-05 13:54:26 +02:00
AGENTS.md downloadable pdf 2026-07-30 10:39:36 +02:00
astro.config.ts move SITE_URL out of source default into each CI workflow 2026-05-05 14:58:34 +02:00
mise.toml add Forgejo + Woodpecker CI alongside GitHub Actions 2026-05-05 13:54:26 +02:00
notes.md rewrite 2025-12-02 23:05:56 +01:00
package.json ci: settle initial Woodpecker run 2026-05-05 14:05:48 +02:00
pnpm-lock.yaml add Forgejo + Woodpecker CI alongside GitHub Actions 2026-05-05 13:54:26 +02:00
pnpm-workspace.yaml rewrite 2025-12-02 23:05:56 +01:00
README.md add audio to glados 2026-02-17 08:36:40 +01:00
renovate.json add Forgejo + Woodpecker CI alongside GitHub Actions 2026-05-05 13:54:26 +02:00
Taskfile.yml ci: settle initial Woodpecker run 2026-05-05 14:05:48 +02:00
tsconfig.json rewrite 2025-12-02 23:05:56 +01:00

morten-olsen.github.io

Personal portfolio and blog built with Astro 5.

Development

pnpm install          # Install dependencies
pnpm dev              # Start dev server (http://localhost:4321)
pnpm build            # Production build
pnpm preview          # Preview production build

Audio Generation

Generate text-to-speech audio versions of blog posts using ElevenLabs and OpenRouter.

Prerequisites

Environment Variables

Variable Required Description
OPENROUTER_API_KEY For prepare OpenRouter API key
ELEVENLABS_API_KEY For generate ElevenLabs API key
ELEVENLABS_VOICE_ID For generate Voice ID for body narration
ELEVENLABS_TITLE_VOICE_ID For generate Voice ID for section titles
ELEVENLABS_MODEL No ElevenLabs model (default: eleven_v3)
OPENROUTER_MODEL No LLM for script cleaning (default: openai/gpt-4.1-mini)

Workflow

The script runs in three steps so you can review and adjust at each stage.

1. Prepare — clean the MDX into a narration script

npx tsx scripts/generate-audio.ts prepare <post-slug>

This sends the post content through an LLM to strip MDX components, code blocks, and markdown formatting, converting it into natural spoken prose. The article title is prepended as the first section heading.

The output is saved to src/content/posts/<slug>/assets/audio-work/script.txt. Open it, review the text, and edit anything that doesn't read well before moving on.

2. Generate — create audio clips

npx tsx scripts/generate-audio.ts generate <post-slug>

Generates one MP3 clip per segment (section titles use ELEVENLABS_TITLE_VOICE_ID, body text uses ELEVENLABS_VOICE_ID). Long body segments are automatically chunked to stay within the ElevenLabs character limit.

Clips are saved to audio-work/clips/ with filenames like 000-title.mp3, 001-body.mp3, etc. Listen to them and regenerate specific segments if needed:

npx tsx scripts/generate-audio.ts generate <post-slug> --only=3,5

If you edited script.txt after the initial generate, the script will re-parse segments automatically.

3. Merge — combine clips into the final audio file

npx tsx scripts/generate-audio.ts merge <post-slug>

Concatenates all clips with 1 second of silence between segments and writes the result to src/content/posts/<slug>/assets/audio.mp3.

Example

export OPENROUTER_API_KEY=sk-or-...
export ELEVENLABS_API_KEY=sk_...
export ELEVENLABS_VOICE_ID=abc123
export ELEVENLABS_TITLE_VOICE_ID=def456

npx tsx scripts/generate-audio.ts prepare node-security
# Review/edit src/content/posts/node-security/assets/audio-work/script.txt

npx tsx scripts/generate-audio.ts generate node-security
# Listen to clips in audio-work/clips/, redo any that sound off
npx tsx scripts/generate-audio.ts generate node-security --only=2

npx tsx scripts/generate-audio.ts merge node-security
# -> src/content/posts/node-security/assets/audio.mp3

To enable the audio player on the post, add audio: ./assets/audio.mp3 to the post's frontmatter.