GitHub Copilot ChatGPT Claude Codex CLI Cursor opencode Skill Text

podcast-generation

Generate AI-powered podcast-style audio narratives using Azure OpenAI's GPT Realtime Mini model via WebSocket. Use when building text-to-speech features, audio narrative generation, podcast creation from content, or integrating with Azure OpenAI Realtime API for real audio output

Ciza · 0 points · 22 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download microsoft-skills-.github_skills_podcast-generation-e58528d.zip · 6 KB
Part of microsoft/skills — 195 skills

Install

skills CLI npx skills add https://github.com/microsoft/skills/tree/main/.github/skills/podcast-generation
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install microsoft-skills@llmmart
Git git clone https://github.com/microsoft/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole microsoft/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Podcast Generation with GPT Realtime Mini

Generate real audio narratives from text content using Azure OpenAI's Realtime API.

Quick Start

  1. Configure environment variables for Realtime API
  2. Connect via WebSocket to Azure OpenAI Realtime endpoint
  3. Send text prompt, collect PCM audio chunks + transcript
  4. Convert PCM to WAV format
  5. Return base64-encoded audio to frontend for playback

Environment Configuration

AZURE_OPENAI_AUDIO_API_KEY=your_realtime_api_key
AZURE_OPENAI_AUDIO_ENDPOINT=https://your-resource.cognitiveservices.azure.com
AZURE_OPENAI_AUDIO_DEPLOYMENT=gpt-realtime-mini

Note: Endpoint should NOT include /openai/v1/ - just the base URL.

Core Workflow

Backend Audio Generation

from openai import AsyncOpenAI
import base64

# Convert HTTPS endpoint to WebSocket URL
ws_url = endpoint.replace("https://", "wss://") + "/openai/v1"

client = AsyncOpenAI(
    websocket_base_url=ws_url,
    api_key=api_key
)

audio_chunks = []
transcript_parts = []

async with client.realtime.connect(model="gpt-realtime-mini") as conn:
    # Configure for audio-only output
    await conn.session.update(session={
        "output_modalities": ["audio"],
        "instructions": "You are a narrator. Speak naturally."
    })
    
    # Send text to narrate
    await conn.conversation.item.create(item={
        "type": "message",
        "role": "user",
        "content": [{"type": "input_text", "text": prompt}]
    })
    
    await conn.response.create()
    
    # Collect streaming events
    async for event in conn:
        if event.type == "response.output_audio.delta":
            audio_chunks.append(base64.b64decode(event.delta))
        elif event.type == "response.output_audio_transcript.delta":
            transcript_parts.append(event.delta)
        elif event.type == "response.done":
            break

# Convert PCM to WAV (see scripts/pcm_to_wav.py)
pcm_audio = b''.join(audio_chunks)
wav_audio = pcm_to_wav(pcm_audio, sample_rate=24000)

Frontend Audio Playback

// Convert base64 WAV to playable blob
const base64ToBlob = (base64, mimeType) => {
  const bytes = atob(base64);
  const arr = new Uint8Array(bytes.length);
  for (let i = 0; i < bytes.length; i++) arr[i] = bytes.charCodeAt(i);
  return new Blob([arr], { type: mimeType });
};

const audioBlob = base64ToBlob(response.audio_data, 'audio/wav');
const audioUrl = URL.createObjectURL(audioBlob);
new Audio(audioUrl).play();

Voice Options

Voice Character
alloy Neutral
echo Warm
fable Expressive
onyx Deep
nova Friendly
shimmer Clear

Realtime API Events

  • response.output_audio.delta - Base64 audio chunk
  • response.output_audio_transcript.delta - Transcript text
  • response.done - Generation complete
  • error - Handle with event.error.message

Audio Format

  • Input: Text prompt
  • Output: PCM audio (24kHz, 16-bit, mono)
  • Storage: Base64-encoded WAV

References

Files (skills)
  • references
    • architecture.md 4.6 KB
      # Architecture Reference
      
      ## Full-Stack Flow
      
      ```
      ┌─────────────────────────────────────────────────────────────────┐
      │                       FRONTEND (React)                          │
      │  AudioNarrative.jsx / PodcastOverview.jsx                       │
      │    ↓ POST /api/v1/ai/audio                                     │
      │  api.js → aiAPI.generateAudio(sourceType, sourceId, ...)       │
      └────────────────────────────┬────────────────────────────────────┘
                                   │
      ┌────────────────────────────▼────────────────────────────────────┐
      │                       BACKEND (FastAPI)                         │
      │  API: ai.py                                                     │
      │    @router.post("/audio") → AudioNarrativeRequest               │
      │    @router.get("/audio/{id}/stream") → WAV file                │
      │                             │                                   │
      │  Service: ai_service.py                                         │
      │    generate_audio_narrative()                                   │
      │    - Fetch content from DB (tag/bookmark/custom)               │
      │    - Build styled prompt                                        │
      │    - WebSocket connect to Azure Realtime API                   │
      │    - Stream audio chunks + transcript                           │
      │    - PCM → WAV conversion                                       │
      │    - Save to database                                           │
      │                             │                                   │
      │  Model: database.py                                             │
      │    AudioNarrative table                                         │
      └────────────────────────────┬────────────────────────────────────┘
                                   │ wss://
      ┌────────────────────────────▼────────────────────────────────────┐
      │              Azure OpenAI Realtime API                          │
      │  Model: gpt-realtime-mini                                       │
      │  Output: PCM audio (24kHz, 16-bit mono) + transcript           │
      └─────────────────────────────────────────────────────────────────┘
      ```
      
      ## Database Schema
      
      ```python
      class AudioNarrative(Base):
          __tablename__ = "audio_narratives"
          
          id: int                          # Primary key
          source_type: str                 # "tag", "bookmark", "custom"
          source_id: Optional[int]         # Reference to source
          source_name: Optional[str]       # Display name
          title: str                       # Generated title
          script: str                      # Transcript text
          audio_url: Optional[str]         # Stream endpoint
          audio_data: Optional[str]        # Base64 WAV
          duration_seconds: Optional[int]  # Calculated from PCM length
          voice_name: str                  # "alloy", "echo", etc.
          created_at: datetime
      ```
      
      ## Pydantic Schemas
      
      ```python
      class AudioNarrativeRequest(BaseModel):
          source_type: str          # Required: "tag", "bookmark", "custom"
          source_id: Optional[Union[int, str]] = None
          custom_query: Optional[str] = None
          voice_name: str = "alloy"
          style: str = "podcast"    # "podcast", "summary", "lecture"
      
      class AudioNarrativeResponse(BaseModel):
          id: int
          title: str
          script: str
          audio_url: Optional[str]
          audio_data: Optional[str]  # Base64 WAV
          duration_seconds: Optional[int]
          voice_name: str
          created_at: datetime
      ```
      
      ## Style Instructions
      
      ```python
      STYLE_INSTRUCTIONS = {
          "podcast": "Speak in a conversational, engaging podcast style with natural transitions. Use phrases like 'Let's dive into...' and 'What's fascinating here is...'",
          "summary": "Speak clearly and informatively, getting straight to the key points in a news anchor style.",
          "lecture": "Speak in an educational, thorough style suitable for learning. Explain concepts clearly like a professor."
      }
      ```
      
    • code-examples.md 6 KB
      # Code Examples
      
      ## Complete Backend Service Method
      
      ```python
      async def generate_audio_narrative(
          self,
          source_type: str,
          source_id: Optional[Union[int, str]] = None,
          custom_query: Optional[str] = None,
          voice_name: str = "alloy",
          style: str = "podcast"
      ) -> Dict[str, Any]:
          """Generate podcast-style audio using gpt-realtime-mini"""
          
          # 1. Validate configuration
          if not settings.azure_openai_audio_api_key:
              raise ValueError("AZURE_OPENAI_AUDIO_API_KEY not configured")
          
          # 2. Gather content based on source
          if source_type == "tag":
              tag = await db.get_tag(source_id)
              bookmarks = tag.bookmarks
              title = f"Exploring {tag.name}"
          elif source_type == "bookmark":
              bookmark = await db.get_bookmark(source_id)
              bookmarks = [bookmark]
              title = f"Deep Dive: {bookmark.title}"
          else:  # custom
              bookmarks = await db.get_recent_bookmarks(limit=10)
              title = f"Research Summary: {custom_query[:50]}"
          
          # 3. Build prompt with style
          content = "\n".join([f"**{b.title}**\n{b.summary}" for b in bookmarks[:10]])
          prompt = f"""Create a {style} narrative from these sources:
      {content}
      
      {STYLE_INSTRUCTIONS[style]}
      Make it 1-2 minutes (150-250 words). Speak naturally."""
          
          # 4. Connect to Realtime API
          ws_url = settings.azure_openai_audio_endpoint.replace("https://", "wss://") + "/openai/v1"
          client = AsyncOpenAI(websocket_base_url=ws_url, api_key=settings.azure_openai_audio_api_key)
          
          audio_chunks, transcript_parts = [], []
          
          async with client.realtime.connect(model=settings.azure_openai_audio_deployment) as conn:
              await conn.session.update(session={
                  "output_modalities": ["audio"],
                  "instructions": f"Narrator creating {style}-style content. Speak naturally, don't ask questions."
              })
              
              await conn.conversation.item.create(item={
                  "type": "message",
                  "role": "user", 
                  "content": [{"type": "input_text", "text": prompt}]
              })
              
              await conn.response.create()
              
              async for event in conn:
                  if event.type == "response.output_audio.delta":
                      audio_chunks.append(base64.b64decode(event.delta))
                  elif event.type == "response.output_audio_transcript.delta":
                      transcript_parts.append(event.delta)
                  elif event.type == "response.done":
                      break
                  elif event.type == "error":
                      raise ValueError(f"Realtime API error: {event.error.message}")
          
          # 5. Process audio
          pcm_audio = b''.join(audio_chunks)
          wav_audio = pcm_to_wav(pcm_audio, sample_rate=24000)
          audio_base64 = base64.b64encode(wav_audio).decode('utf-8')
          duration = len(pcm_audio) // (24000 * 2)  # 24kHz, 16-bit
          
          # 6. Save and return
          narrative = AudioNarrative(
              source_type=source_type,
              title=title,
              script=''.join(transcript_parts),
              audio_data=audio_base64,
              duration_seconds=duration,
              voice_name=voice_name
          )
          db.add(narrative)
          await db.commit()
          
          return {
              "id": narrative.id,
              "title": title,
              "script": narrative.script,
              "audio_data": audio_base64,
              "audio_url": f"/api/v1/ai/audio/{narrative.id}/stream",
              "duration_seconds": duration,
              "voice_name": voice_name
          }
      ```
      
      ## FastAPI Endpoints
      
      ```python
      @router.post("/audio", response_model=AudioNarrativeResponse)
      async def generate_audio(request: AudioNarrativeRequest, db: AsyncSession = Depends(get_db)):
          ai_service = AIService(db)
          result = await ai_service.generate_audio_narrative(
              source_type=request.source_type,
              source_id=request.source_id,
              custom_query=request.custom_query,
              voice_name=request.voice_name,
              style=request.style
          )
          return AudioNarrativeResponse(**result)
      
      @router.get("/audio/{narrative_id}/stream")
      async def stream_audio(narrative_id: int, db: AsyncSession = Depends(get_db)):
          ai_service = AIService(db)
          narrative = await ai_service.get_audio_narrative_by_id(narrative_id)
          if not narrative:
              raise HTTPException(404, "Not found")
          
          audio_bytes = base64.b64decode(narrative["audio_data"])
          return Response(
              content=audio_bytes,
              media_type="audio/wav",
              headers={"Content-Disposition": f'filename="narrative-{narrative_id}.wav"'}
          )
      ```
      
      ## React Component Pattern
      
      ```jsx
      function AudioPlayer({ sourceType, sourceId }) {
        const [narrative, setNarrative] = useState(null);
        const [loading, setLoading] = useState(false);
        const [isPlaying, setIsPlaying] = useState(false);
        const audioRef = useRef(null);
      
        const generate = async () => {
          setLoading(true);
          const response = await aiAPI.generateAudio(sourceType, sourceId, null, 'alloy', 'podcast');
          setNarrative(response.data);
          setLoading(false);
        };
      
        const play = () => {
          if (!narrative?.audio_data) return;
          
          const blob = base64ToBlob(narrative.audio_data, 'audio/wav');
          const url = URL.createObjectURL(blob);
          
          if (!audioRef.current) audioRef.current = new Audio();
          audioRef.current.src = url;
          audioRef.current.onended = () => setIsPlaying(false);
          audioRef.current.play();
          setIsPlaying(true);
        };
      
        return (
          <div>
            {!narrative && <button onClick={generate} disabled={loading}>Generate Podcast</button>}
            {narrative && (
              <>
                <button onClick={play}>{isPlaying ? 'Pause' : 'Play'}</button>
                <p>{narrative.script}</p>
              </>
            )}
          </div>
        );
      }
      ```
      
      ## Frontend API Service
      
      ```javascript
      export const aiAPI = {
        generateAudio: (sourceType, sourceId = null, customQuery = null, 
                        voiceName = 'alloy', style = 'podcast') =>
          api.post('/ai/audio', {
            source_type: sourceType,
            source_id: sourceId,
            custom_query: customQuery,
            voice_name: voiceName,
            style
          }),
        
        listAudioNarratives: (limit = 10) => api.get(`/ai/audio?limit=${limit}`),
      };
      ```
      
  • scripts
    • pcm_to_wav.py 2.4 KB
      #!/usr/bin/env python3
      """Convert raw PCM audio data to WAV format.
      
      Azure OpenAI Realtime API outputs PCM audio (24kHz, 16-bit, mono).
      This script converts it to standard WAV format for browser playback.
      
      Usage:
          from pcm_to_wav import pcm_to_wav
          wav_bytes = pcm_to_wav(pcm_data, sample_rate=24000)
      """
      
      import struct
      import io
      
      
      def pcm_to_wav(
          pcm_data: bytes,
          sample_rate: int = 24000,
          channels: int = 1,
          sample_width: int = 2
      ) -> bytes:
          """Convert raw PCM audio data to WAV format.
          
          Args:
              pcm_data: Raw PCM audio bytes
              sample_rate: Samples per second (default 24000 for gpt-realtime-mini)
              channels: Number of audio channels (default 1 for mono)
              sample_width: Bytes per sample (default 2 for 16-bit)
          
          Returns:
              WAV-formatted audio bytes
          """
          wav_buffer = io.BytesIO()
          
          # RIFF header
          wav_buffer.write(b'RIFF')
          wav_buffer.write(struct.pack('<I', 36 + len(pcm_data)))  # File size - 8
          wav_buffer.write(b'WAVE')
          
          # fmt subchunk
          wav_buffer.write(b'fmt ')
          wav_buffer.write(struct.pack('<I', 16))                    # Subchunk size
          wav_buffer.write(struct.pack('<H', 1))                     # Audio format (1 = PCM)
          wav_buffer.write(struct.pack('<H', channels))              # Number of channels
          wav_buffer.write(struct.pack('<I', sample_rate))           # Sample rate
          wav_buffer.write(struct.pack('<I', sample_rate * channels * sample_width))  # Byte rate
          wav_buffer.write(struct.pack('<H', channels * sample_width))  # Block align
          wav_buffer.write(struct.pack('<H', sample_width * 8))      # Bits per sample
          
          # data subchunk
          wav_buffer.write(b'data')
          wav_buffer.write(struct.pack('<I', len(pcm_data)))
          wav_buffer.write(pcm_data)
          
          return wav_buffer.getvalue()
      
      
      def calculate_duration(pcm_data: bytes, sample_rate: int = 24000, sample_width: int = 2) -> int:
          """Calculate audio duration in seconds from PCM data length."""
          return len(pcm_data) // (sample_rate * sample_width)
      
      
      if __name__ == "__main__":
          # Example usage
          import sys
          if len(sys.argv) > 1:
              with open(sys.argv[1], 'rb') as f:
                  pcm_data = f.read()
              wav_data = pcm_to_wav(pcm_data)
              output_path = sys.argv[1].replace('.pcm', '.wav')
              with open(output_path, 'wb') as f:
                  f.write(wav_data)
              print(f"Converted to {output_path} ({calculate_duration(pcm_data)}s)")
      
  • SKILL.md 3.7 KB
    ---
    name: podcast-generation
    description: Generate AI-powered podcast-style audio narratives using Azure OpenAI's GPT Realtime Mini model via WebSocket. Use when building text-to-speech features, audio narrative generation, podcast creation from content, or integrating with Azure OpenAI Realtime API for real audio output. Covers full-stack implementation from React frontend to Python FastAPI backend with WebSocket streaming.
    ---
    
    # Podcast Generation with GPT Realtime Mini
    
    Generate real audio narratives from text content using Azure OpenAI's Realtime API.
    
    ## Quick Start
    
    1. Configure environment variables for Realtime API
    2. Connect via WebSocket to Azure OpenAI Realtime endpoint
    3. Send text prompt, collect PCM audio chunks + transcript
    4. Convert PCM to WAV format
    5. Return base64-encoded audio to frontend for playback
    
    ## Environment Configuration
    
    ```env
    AZURE_OPENAI_AUDIO_API_KEY=your_realtime_api_key
    AZURE_OPENAI_AUDIO_ENDPOINT=https://your-resource.cognitiveservices.azure.com
    AZURE_OPENAI_AUDIO_DEPLOYMENT=gpt-realtime-mini
    ```
    
    **Note**: Endpoint should NOT include `/openai/v1/` - just the base URL.
    
    ## Core Workflow
    
    ### Backend Audio Generation
    
    ```python
    from openai import AsyncOpenAI
    import base64
    
    # Convert HTTPS endpoint to WebSocket URL
    ws_url = endpoint.replace("https://", "wss://") + "/openai/v1"
    
    client = AsyncOpenAI(
        websocket_base_url=ws_url,
        api_key=api_key
    )
    
    audio_chunks = []
    transcript_parts = []
    
    async with client.realtime.connect(model="gpt-realtime-mini") as conn:
        # Configure for audio-only output
        await conn.session.update(session={
            "output_modalities": ["audio"],
            "instructions": "You are a narrator. Speak naturally."
        })
        
        # Send text to narrate
        await conn.conversation.item.create(item={
            "type": "message",
            "role": "user",
            "content": [{"type": "input_text", "text": prompt}]
        })
        
        await conn.response.create()
        
        # Collect streaming events
        async for event in conn:
            if event.type == "response.output_audio.delta":
                audio_chunks.append(base64.b64decode(event.delta))
            elif event.type == "response.output_audio_transcript.delta":
                transcript_parts.append(event.delta)
            elif event.type == "response.done":
                break
    
    # Convert PCM to WAV (see scripts/pcm_to_wav.py)
    pcm_audio = b''.join(audio_chunks)
    wav_audio = pcm_to_wav(pcm_audio, sample_rate=24000)
    ```
    
    ### Frontend Audio Playback
    
    ```javascript
    // Convert base64 WAV to playable blob
    const base64ToBlob = (base64, mimeType) => {
      const bytes = atob(base64);
      const arr = new Uint8Array(bytes.length);
      for (let i = 0; i < bytes.length; i++) arr[i] = bytes.charCodeAt(i);
      return new Blob([arr], { type: mimeType });
    };
    
    const audioBlob = base64ToBlob(response.audio_data, 'audio/wav');
    const audioUrl = URL.createObjectURL(audioBlob);
    new Audio(audioUrl).play();
    ```
    
    ## Voice Options
    
    | Voice | Character |
    |-------|-----------|
    | alloy | Neutral |
    | echo | Warm |
    | fable | Expressive |
    | onyx | Deep |
    | nova | Friendly |
    | shimmer | Clear |
    
    ## Realtime API Events
    
    - `response.output_audio.delta` - Base64 audio chunk
    - `response.output_audio_transcript.delta` - Transcript text
    - `response.done` - Generation complete
    - `error` - Handle with `event.error.message`
    
    ## Audio Format
    
    - **Input**: Text prompt
    - **Output**: PCM audio (24kHz, 16-bit, mono)
    - **Storage**: Base64-encoded WAV
    
    ## References
    
    - **Full architecture**: See [references/architecture.md](references/architecture.md) for complete stack design
    - **Code examples**: See [references/code-examples.md](references/code-examples.md) for production patterns
    - **PCM conversion**: Use [scripts/pcm_to_wav.py](scripts/pcm_to_wav.py) for audio format conversion
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related