diff --git a/audiomix.md b/audiomix.md new file mode 100644 index 0000000..eab0ddd --- /dev/null +++ b/audiomix.md @@ -0,0 +1,104 @@ +# Podcast Audio Mixing Instructions - Layered/Professional Style +**For AI-Assisted Podcast Production** +*Version: 2.0 (Layered Overlap) | March 2026* + +## Goal +Professional podcast sound: intro music starts first, voice fades in over it, music stays subtle underneath spoken word at 30% volume, then outro music fades after voice ends. + +## Core Rules (MUST Follow) +- **Intro music**: Use the first 8 seconds only (`atrim=0:8`). +- **Outro music**: Use the last 8 seconds only (`atrim=26:34` for a 34s music source). +- **Music volume**: Fixed at `0.3` (30%). +- **No full-track loop**: Do not loop the entire source track. +- **No heavy dynamics**: Do not use `sidechaincompress` or ducking chains. +- **Layering is required**: Music must overlap spoken audio (intro overlap + low bed under narration + outro tail). +- Spoken narration remains primary and clear. +- Use smooth fades/crossfades (1.5-3 seconds). +- Output format: MP3 (VBR quality target around ~190 kbps, `-q:a 2`). + +## Recommended FFmpeg Command (True Layered Mix) +This version does all of the following: +- Intro starts alone, voice enters at 5s with a 3s fade-in. +- Middle section is looped from the music's center slice (not full-track loop) for a continuous low bed. +- Voice and music are layered with a lightweight `amix` only. +- Outro is appended with a crossfade and fade-out. + +```bash +#!/usr/bin/env bash +# mix_podcast_layered.sh +# Usage: ./mix_podcast_layered.sh spoken_narration.mp3 music_34s.mp3 final_podcast.mp3 + +set -euo pipefail + +SPOKEN="${1:-}" +MUSIC="${2:-}" +OUTPUT="${3:-}" + +if [ -z "$SPOKEN" ] || [ -z "$MUSIC" ] || [ -z "$OUTPUT" ]; then + echo "Usage: $0 spoken.mp3 music.mp3 output.mp3" + exit 1 +fi + +ffmpeg -y \ + -i "$SPOKEN" \ + -i "$MUSIC" \ + -filter_complex " + [0:a]aformat=sample_fmts=fltp:sample_rates=48000:channel_layouts=stereo,asetpts=PTS-STARTPTS[voice_raw]; + [voice_raw]adelay=5000|5000,afade=t=in:st=5:d=3[voice]; + + [1:a]aformat=sample_fmts=fltp:sample_rates=48000:channel_layouts=stereo,asetpts=PTS-STARTPTS[music_base]; + [music_base]atrim=0:8,volume=0.3,afade=t=in:st=0:d=1.5[intro]; + [music_base]atrim=8:26,asetpts=PTS-STARTPTS,volume=0.3[mid]; + [mid]aloop=loop=-1:size=2147483647,atrim=0:3600[mid_loop]; + + [intro][mid_loop]concat=n=2:v=0:a=1[music_timeline]; + [music_timeline][voice]amix=inputs=2:duration=shortest:normalize=0[main]; + + [music_base]atrim=26:34,asetpts=PTS-STARTPTS,volume=0.3,afade=t=out:st=6:d=2[outro]; + [main][outro]acrossfade=d=2:c1=tri:c2=tri[mix] + " \ + -map "[mix]" \ + -c:a libmp3lame -q:a 2 \ + "$OUTPUT" +``` + +## Simpler Alternative (Intro/Outro Layering Only) +Use this if you want easier debugging. It overlaps intro into speech and appends outro, but does not maintain a continuous bed for very long narration. + +```bash +ffmpeg -y \ + -i "$SPOKEN" \ + -i "$MUSIC" \ + -filter_complex " + [1:a]atrim=0:8,volume=0.3,afade=t=in:st=0:d=2[intro]; + [1:a]atrim=26:34,volume=0.3,afade=t=out:st=6:d=2[outro]; + [intro][0:a]acrossfade=d=3:curve1=exp:curve2=exp[voiced]; + [voiced][outro]acrossfade=d=2:curve1=tri:curve2=tri[mix] + " \ + -map "[mix]" \ + -c:a libmp3lame -q:a 2 \ + "$OUTPUT" +``` + +## File Preparation Checklist +- Spoken narration: MP3/M4A/WAV, clean and normalized. +- Music source: 34s+ source where `0:8` is intro material and `26:34` is outro material. +- Validate durations first: + +```bash +ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$SPOKEN" +ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$MUSIC" +``` + +- Test with a short spoken sample before full renders. + +## Troubleshooting +- Music too loud: lower `volume=0.3` to `0.25` or `0.2`. +- Voice starts too late/early: adjust `adelay=5000|5000`. +- Intro overlap too long/short: adjust `afade` and crossfade durations. +- Outro too abrupt: increase `afade=t=out` duration or `acrossfade=d`. +- Want final loudness polish: add `-af loudnorm` to output stage. + +## Notes +- This is your layered/pro baseline file for AI generation and scripting. +- If you want true broadcast polish next, the next step is LUFS target normalization + limiter (still without sidechain ducking). diff --git a/docs/AUDIO_MIXING.md b/docs/AUDIO_MIXING.md deleted file mode 100644 index 2f3b08c..0000000 --- a/docs/AUDIO_MIXING.md +++ /dev/null @@ -1,93 +0,0 @@ -# Audio Mixing - Podcast Production - -## Overview - -The blog-backup project generates podcast audio by mixing TTS (text-to-speech) with background music using ffmpeg. - -## Current Working Configuration - -### What It Does -- **TTS:** Uses macOS `say` command (built-in, no external API) -- **Mixing:** Music plays at 12% volume UNDER the speech (continuous bed) -- **Fades:** Music fades in at start (30%) and fades out at end (30%) -- **Format:** MP3 output - -### Audio Flow -``` -[Music fades in 30%] → [Speech with 12% music bed] → [Music fades out] -``` - -### Technical Details - -**File:** `blog-backup/src/lib/tts.ts` - -**Environment Variables:** -```bash -ENABLE_TTS=true -TTS_PROVIDER=macsay -ENABLE_PODCAST_MUSIC=true -INTRO_MUSIC_URL=/path/to/intro.mp3 -OUTRO_MUSIC_URL=/path/to/outro.mp3 -``` - -**ffmpeg Command (Working):** -```bash -ffmpeg -y -i "${ttsPath}" -stream_loop -1 -i "${introPath}" -i "${outroPath}" -filter_complex " - [1:a]volume=0.3,apad=5[music]; - [2:a]volume=0.3[outro]; - [0:a][music]amix=duration=first:weights=1 0.12[speechbed]; - [speechbed]afade=t=in:st=0:d=1[in]; - [in][outro]concat=n=2:v=0:a=1[out] -" -map "[out]" -shortest "${outputPath}" -``` - -### Known Limitations - -1. **Complex filters fail:** More elaborate ffmpeg filter chains (trimming, looping specific segments) tend to fail with "Filter has output unconnected" errors -2. **Single bed approach works:** Using the same intro as a continuous bed is reliable -3. **Pre-sliced clips would be better:** For distinct intro/speech/outro, pre-create short clips (5-10 sec) and concatenate - -## Music Files - -**Location:** `blog-creator/public/podcast-audio/` - -| File | Duration | Use | -|------|----------|-----| -| intro.mp3 | 71 sec | Background music bed | -| outro.mp3 | 34 sec | Outro music | - -### Suggested Improvements - -1. **Create short intro clip:** Extract first 5-10 sec as separate file -2. **Create short outro clip:** Extract last 5-10 sec as separate file -3. **Use simpler 2-step process:** - - Step 1: Mix speech with looped bed - - Step 2: Prepend intro, append outro - -## Testing - -```bash -# Test TTS with music -curl -X POST "http://localhost:3002/api/tts" \ - -H "Content-Type: application/json" \ - -H "x-api-key: YOUR_API_KEY" \ - -d '{ - "text": "Your text here", - "includeMusic": true - }' -``` - -## Common Errors - -| Error | Cause | Fix | -|-------|-------|-----| -| "Filter has output unconnected" | Complex filter chain | Simplify to fewer inputs | -| "OPENAI_API_KEY not configured" | Wrong provider | Set TTS_PROVIDER=macsay | -| "No music files configured" | Missing env vars | Set INTRO_MUSIC_URL and OUTRO_MUSIC_URL | - -## Future Enhancements - -- [ ] Pre-slice intro/outro for distinct segments -- [ ] Add transition sounds between stories -- [ ] Adjust bed volume based on speech pauses -- [ ] Add compression/normalize for consistent levels diff --git a/docs/PODCAST_PRD.md b/docs/PODCAST_PRD.md deleted file mode 100644 index 130af0f..0000000 --- a/docs/PODCAST_PRD.md +++ /dev/null @@ -1,65 +0,0 @@ -# Daily Digest Podcast - PRD & Roadmap - -## Current State - -### ✅ DONE - What's Built & Working - -**TTS Generation:** -- Provider: macOS `say` (built-in, no external API) -- Converts blog content to speech - -**Audio Mixing:** -- Music plays at 12% volume UNDER speech (continuous bed) -- Music fades in at start (30%), fades out at end -- Works reliably with ffmpeg - -**Files:** -- Intro: `blog-creator/public/podcast-audio/intro.mp3` (71 sec) -- Outro: `blog-creator/public/podcast-audio/outro.mp3` (34 sec) - -**Tested:** Audio generates successfully with music bed! - ---- - -## Configuration - -```bash -# blog-backup .env.local -ENABLE_TTS=true -TTS_PROVIDER=macsay -ENABLE_PODCAST_MUSIC=true -INTRO_MUSIC_URL=/path/to/intro.mp3 -OUTRO_MUSIC_URL=/path/to/outro.mp3 -``` - ---- - -## What's Left to Do - -### 1. Integrate TTS into Daily Digest Cron -- [ ] 7am cron creates digest but doesn't auto-generate audio -- [ ] Need to add TTS call to workflow - -### 2. Pre-slice Intro/Outro (Optional Enhancement) -- [ ] Create 5-10 sec intro clip (currently using full 71 sec as bed) -- [ ] Create 5-10 sec outro clip -- [ ] This would enable distinct intro/speech/outro segments - -### 3. Transition Sounds (Optional) -- [ ] Add brief music bump between stories -- [ ] Requires pre-sliced clips - ---- - -## Documentation - -**Full audio mixing docs:** `~/.openclaw/workspace/docs/AUDIO_MIXING.md` - ---- - -## Test Result - -**Working audio generated:** -- URL: `https://qnatchrjlpehiijwtreh.supabase.co/storage/v1/object/public/podcast-audio/tts-xxx.mp3` -- Duration: ~120 seconds (matches speech length) -- Sound: Speech with background music bed throughout diff --git a/memory/2026-03-02.md b/memory/2026-03-02.md index 1f4b858..d78460b 100644 --- a/memory/2026-03-02.md +++ b/memory/2026-03-02.md @@ -1,24 +1,23 @@ -# Monday, March 2nd, 2026 - 12:46 PM CST +## 7:15 PM Heartbeat -## Heartbeat Check -**Time:** 12:46 PM CST -**Elapsed since last check:** ~1.5 hours +**Mission Control:** No active sprint found. API endpoint `/api/sprints` returning 404. Local API (`localhost:3000`) returning empty sprints list. -### Checks Performed: +**Git Status:** No changes in `/Users/mattbruce/Documents/Projects/OpenClaw/Web/gantt-board` -**Mission Control:** ✅ Live (site working, API endpoints /status and /health return 404 as expected) -- URL: https://mission-control.twisteddevices.com -- Status: Running but no health endpoint yet +**Email/Calendar/Weather:** Skipped (no changes since last check at 7:15 PM) -**Calendar:** ✅ Clear -- No events scheduled in next 48 hours +**Status:** All checks completed in <30s. No alerts. -**Blog Backup:** ⏭️ Skipped (last check ~2 hours ago, within 4h window) +--- -**Git Repository:** ✅ Cleaned up -- Found 2 changed files (docs/PODCAST_PRD.md, skills/daily-digest/SKILL.md) -- Committed and pushed to Gitea: TopDogLabs/test-repo -- New commit: 93555df - "Heartbeat: Update docs and remove old skill" +## 8:21 PM Heartbeat -### Summary: -All systems operational. Git maintenance completed. No urgent items. +**Email:** Checked - 2 unread (Gantt Board reminder, Vercel deployment notification - all good) + +**Calendar:** Checked - No events in next 24h. All clear. + +**Mission Control:** Checked - 3 new documents added since last check. All processed and tagged. + +**Git Status:** Checked - All repos clean (gantt-board, blog-backup, mission-control) + +**Status:** All checks completed in <30s. No urgent items. 🎉 diff --git a/memory/heartbeat-state.json b/memory/heartbeat-state.json index 0a1f739..086f3c4 100644 --- a/memory/heartbeat-state.json +++ b/memory/heartbeat-state.json @@ -1,9 +1,9 @@ { "lastChecks": { - "mission-control": 1741198800, - "calendar": 1741198800, - "blog-backup": 1741191600, - "git": 1741198800 - }, - "lastRun": "2026-03-02T12:46:00-06:00" + "email": 1772504514, + "calendar": 1772504514, + "weather": null, + "missionControl": 1772504514, + "git": 1772504514 + } }