Convert Azure documentation pages into engaging podcast-style audio β on-the-go learning powered by Azure OpenAI and Azure Speech services.
"Learn Azure while you commute." β Paste a docs URL, get a podcast MP3.
| Feature | Description |
|---|---|
| π Two podcast styles | Single narrator summary or two-host conversational discussion (Alex & Sam) |
| π Smart content extraction | Automatically scrapes and cleans Azure docs pages β strips nav, scripts, and boilerplate |
| π£οΈ Natural-sounding voices | Azure Neural TTS with expressive chat style β not robotic |
| π Multi-voice SSML | Two distinct voices for conversational mode with automatic voice-switching |
| π Script preview | Generate and review scripts before audio synthesis (--script-only) |
| π Passwordless auth | Uses DefaultAzureCredential β no API keys in code |
| Requirement | Details |
|---|---|
| Python | 3.10 or later |
| Azure CLI | az login for local authentication |
| Azure Developer CLI | azd for infrastructure deployment |
| Azure Subscription | With permissions to create Cognitive Services, Storage, Key Vault |
cd podcast
pip install -r requirements.txtazd init -e podcast-dev
azd env set AZURE_SUBSCRIPTION_ID <your-subscription-id>
azd env set AZURE_LOCATION eastus2
azd provision --no-prompt# Auto-populate .env from deployed resource outputs
azd env get-values > .envOr copy .env.example to .env and fill in values manually.
az login# Two-host conversation (default) β Alex & Sam discuss the topic
python -m src.cli generate "https://learn.microsoft.com/azure/container-apps/overview"
# Single narrator β concise summary
python -m src.cli generate --style single "https://learn.microsoft.com/azure/functions/functions-overview"
# Script only β review before synthesis
python -m src.cli generate --script-only "https://learn.microsoft.com/azure/aks/intro-kubernetes"
# Custom output path
python -m src.cli generate -o my-podcast.mp3 "https://learn.microsoft.com/azure/ai-services/openai/overview"python -m src.cli preview "https://learn.microsoft.com/azure/ai-services/openai/overview"Usage: python -m src.cli [COMMAND] [OPTIONS] URL
Commands:
generate Generate a podcast from an Azure documentation URL
preview Preview extracted content (no Azure resources needed)
Generate Options:
--style [single|conversation] Podcast style (default: conversation)
--output, -o PATH Output MP3 file path
--script-only Only generate the script, no audio
--help Show help
DocTalk is deployed as a cloud API on Azure Container Apps with async job processing.
API Endpoint: https://ca-doctalk-api-mkffp6.wittyfield-14310482.eastus2.azurecontainerapps.io
# Submit a podcast generation job
curl -X POST https://ca-doctalk-api-mkffp6.wittyfield-14310482.eastus2.azurecontainerapps.io/generate \
-H "Content-Type: application/json" \
-d '{"url": "https://learn.microsoft.com/azure/container-apps/overview"}'
# Poll job status
curl https://ca-doctalk-api-mkffp6.wittyfield-14310482.eastus2.azurecontainerapps.io/jobs/{job_id}
# List all jobs
curl https://ca-doctalk-api-mkffp6.wittyfield-14310482.eastus2.azurecontainerapps.io/jobs
# Health check
curl https://ca-doctalk-api-mkffp6.wittyfield-14310482.eastus2.azurecontainerapps.io/healthSee ARCHITECTURE.md for the full design document with diagrams.
Phase 1 (Local): URL βββΆ Scraper βββΆ Azure OpenAI βββΆ Azure Speech TTS βββΆ MP3 Output
Phase 2 (Cloud): Client βββΆ FastAPI βββΆ Storage Queue βββΆ Worker βββΆ Blob Storage
(ACA) (ACA + KEDA)
| Resource | Service | SKU | Purpose |
|---|---|---|---|
| Azure OpenAI | Microsoft.CognitiveServices/accounts |
S0 | GPT-5.1 model for script generation |
| Azure Speech | Microsoft.CognitiveServices/accounts |
S0 | Neural TTS with multi-voice SSML |
| Blob Storage | Microsoft.Storage/storageAccounts |
Standard_LRS | Store generated podcast MP3s |
| Container Registry | Microsoft.ContainerRegistry/registries |
Basic | Docker images for API + Worker |
| Container Apps (API) | Microsoft.App/containerApps |
Consumption | FastAPI backend with external ingress |
| Container Apps (Worker) | Microsoft.App/containerApps |
Consumption | KEDA queue-triggered worker (scale 0β5) |
| Key Vault | Microsoft.KeyVault/vaults |
Standard | Secrets management |
| Log Analytics | Microsoft.OperationalInsights/workspaces |
PerGB2018 | Centralized logging |
| App Insights | Microsoft.Insights/components |
β | Monitoring & APM |
podcast/
βββ .azure/
β βββ deployment-plan.md # Azure deployment plan (source of truth)
βββ infra/ # Bicep infrastructure-as-code
β βββ main.bicep # Subscription-scoped orchestrator
β βββ main.parameters.json # Deployment parameters
β βββ modules/
β βββ monitoring.bicep # Log Analytics + Application Insights
β βββ keyvault.bicep # Azure Key Vault
β βββ openai.bicep # Azure OpenAI + GPT-5.1 deployment
β βββ speech.bicep # Azure Speech Services
β βββ storage.bicep # Blob + Queue + Table Storage
β βββ registry.bicep # Azure Container Registry
β βββ container-app-env.bicep # Container Apps Environment
β βββ container-app-api.bicep # API Container App
β βββ container-app-worker.bicep # Worker Container App (KEDA)
β βββ identity.bicep # Managed Identities + RBAC
βββ src/
β βββ core/ # Shared pipeline modules
β β βββ pipeline.py # Full scrapeβscriptβTTSβblob pipeline
β β βββ scraper.py # Azure docs URL scraper
β β βββ script_generator.py # Azure OpenAI script generation
β β βββ speech_synthesizer.py # Azure Speech SSML synthesis
β βββ api/
β β βββ main.py # FastAPI backend (POST /generate, GET /jobs)
β βββ worker/
β β βββ main.py # Queue consumer with KEDA scaling
β βββ cli.py # Click-based CLI (local + remote modes)
β βββ config.py # Environment variable configuration
βββ output/ # Generated podcast MP3 files (local mode)
βββ Dockerfile.api # API container image
βββ Dockerfile.worker # Worker container image
βββ azure.yaml # Azure Developer CLI configuration
βββ requirements.txt # Python dependencies
βββ .dockerignore # Build context exclusions
βββ ARCHITECTURE.md # Architecture document with diagrams
βββ ROADMAP.md # Project roadmap
βββ README.md # This file
| Environment | Method | Setup |
|---|---|---|
| Local development | DefaultAzureCredential |
Run az login β picks up CLI credentials |
| CI/CD | Service Principal | Set AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET |
| Production (Azure-hosted) | Managed Identity | Assign ManagedIdentityCredential β no secrets to manage |
Security note: The app uses passwordless authentication via
azure-identity. No API keys are stored in code. For Speech Service, the app constructs an AAD token using the resource ID.
| Variable | Required | Default | Description |
|---|---|---|---|
AZURE_OPENAI_ENDPOINT |
β | β | Azure OpenAI endpoint URL |
AZURE_OPENAI_DEPLOYMENT_NAME |
β | gpt-51 |
Model deployment name |
AZURE_SPEECH_REGION |
β | eastus2 |
Azure region for Speech Service |
AZURE_SPEECH_RESOURCE_ID |
β | β | Speech resource ID (for AAD token auth) |
AZURE_SPEECH_KEY |
β | β | Speech subscription key (alternative to AAD) |
AZURE_STORAGE_ACCOUNT_NAME |
β | β | Storage account for podcast uploads |
AZURE_STORAGE_CONTAINER_NAME |
β | podcasts |
Blob container name |
| Issue | Cause | Solution |
|---|---|---|
AZURE_OPENAI_ENDPOINT is required |
Missing .env config |
Run azd env get-values > .env |
Unsupported parameter: max_tokens |
Old model API parameter | Ensure max_completion_tokens is used (fixed in code) |
SSML must contain a maximum of 50 voice elements |
Too many dialogue lines | Fixed β code merges same-speaker lines and chunks SSML |
Speech synthesis canceled: Error 1007 |
SSML voice limit exceeded | Fixed β automatic chunking with MP3 concatenation |
WinError 32: file in use |
Speech SDK file lock on temp files | Fixed β uses tempfile.mkdtemp() with retry cleanup |
| Unicode errors on Windows console | Emoji characters in output | Fixed β sys.stdout.reconfigure(encoding="utf-8") |
AuthenticationError |
Not logged in to Azure | Run az login |
| Empty content extracted | Page requires authentication | Some docs pages require sign-in; try a public page |
This project is for internal use. See your organization's licensing policies.