---
title: "Cost calculation update: Duration-based pricing"
source: PostHog Documentation
source_url: https://github.com/PostHog/posthog.com
licence: MIT
domain: data-and-analysis
subdomain: posthog-docs
date_added: 2026-04-25
---

# Cost calculation update: Duration-based pricing

## What changed

The cost tracking system now calculates costs based on **actual audio duration** instead of character count.

### Why?

Your ElevenLabs pricing is:
- **$0.30 per 1000 credits**
- **100,000 credits ≈ 100 minutes of audio**
- **Therefore: $0.30 per minute of generated audio**

While ElevenLabs *does* charge per character (1 character = 1 credit), the relationship between characters and audio duration varies based on:
- Speech rate
- Pauses and breaks
- Language
- Voice settings

**Duration-based calculation is more accurate** because it uses the actual billable output.

## New cost file format

```json
{
  "audio_duration_seconds": 326.34,
  "audio_duration_minutes": 5.44,
  "credits_used": 5439,
  "character_count": 5751,
  "chunks_count": 1,
  "request_ids": ["abc123..."],
  "estimated_cost_usd": 1.6317,
  "cost_per_minute": 0.30,
  "plan_details": "100,000 credits ≈ 100 min audio @ $0.30/1000 credits ($0.30/min)"
}
```

### Key fields

- **`audio_duration_seconds`** - Actual MP3 duration
- **`audio_duration_minutes`** - Duration in minutes (used for cost calc)
- **`credits_used`** - Calculated as `duration_minutes × 1000`
- **`estimated_cost_usd`** - Calculated as `duration_minutes × $0.30`
- **`character_count`** - Still tracked for reference

## How it works

### 1. Audio duration detection

After generating audio, the system:

```python
from mutagen.mp3 import MP3
import io

# Parse MP3 to get duration
audio_io = io.BytesIO(audio_data)
audio = MP3(audio_io)
duration_seconds = audio.info.length
```

### 2. Cost calculation

```python
duration_minutes = duration_seconds / 60
credits_used = duration_minutes * 1000  # 1000 credits per minute
cost = duration_minutes * 0.30  # $0.30 per minute
```

### 3. Fallback for edge cases

If duration detection fails (missing library, corrupted file), the system:
- Falls back to character-based estimation
- Shows a warning in the output
- Adds a note to the cost file

## Real-world accuracy

Testing with your existing 10 audio files:

```
Total audio: 99.76 minutes
Total cost: $27.66
Effective rate: $0.277/min
```

This is **very close to the expected $0.30/min** rate, confirming the accuracy of duration-based calculation.

## Requirements

### New dependency: mutagen

Added to `pyproject.toml`:

```toml
dependencies = [
  ...
  "mutagen>=1.47.0",  # ← NEW: For MP3 duration detection
]
```

Install with:
```bash
cd scripts/hogfm
uv sync
```

## Usage

### Automatic (during generation)

Cost tracking is automatic:

```bash
uv run handbook-audio contents/handbook/values.md
```

Output:
```
💾 Saved to: public/handbook-audio/values.mp3 (5.44 min)
💰 Saved cost metrics to: public/handbook-audio/values.cost.json
    Duration: 5.44 min | Credits: 5,439 | Cost: $1.6317
```

### Regenerate costs for existing files

```bash
cd scripts/hogfm
uv run python handbook/regenerate_costs.py
```

This will:
- Scan all existing MP3 files in `public/handbook-audio/`
- Parse each file to get duration
- Generate/update `.cost.json` files with accurate costs

## Analysis commands

### Total cost

```bash
find public/handbook-audio -name "*.cost.json" -exec jq -r '.estimated_cost_usd' {} + | awk '{sum+=$1} END {printf "Total: $%.2f\n", sum}'
```

### Total audio duration

```bash
find public/handbook-audio -name "*.cost.json" -exec jq -r '.audio_duration_minutes' {} + | awk '{sum+=$1} END {printf "%.2f min (%.2f hours)\n", sum, sum/60}'
```

### Most expensive pages

```bash
find public/handbook-audio -name "*.cost.json" -exec sh -c 'echo "$(jq -r .estimated_cost_usd "$1") $(jq -r .audio_duration_minutes "$1") $1"' _ {} \; | sort -rn | head -10 | awk '{printf "$%.2f (%s min) - %s\n", $1, $2, $3}'
```

### Cost per minute analysis

```bash
find public/handbook-audio -name "*.cost.json" -exec jq -r '[.estimated_cost_usd, .audio_duration_minutes] | @csv' {} + | awk -F, '{print $1/$2}' | awk '{sum+=$1; count++} END {printf "Average rate: $%.4f/min\n", sum/count}'
```

## Code changes

### Files modified

1. **`audio_saver.py`**
   - Added `get_audio_duration_seconds()` function
   - Modified `save_audio_file()` to return duration
   - Updated `save_cost_file()` to calculate cost from duration

2. **`generate.py`**
   - Updated to pass audio duration to `save_cost_file()`

3. **`pyproject.toml`**
   - Added `mutagen>=1.47.0` dependency

### Files created

1. **`regenerate_costs.py`** - Utility to recalculate costs for existing audio

## Comparison: Character-based vs Duration-based

### Example: values.md

| Metric | Character-based | Duration-based (actual) |
|--------|----------------|------------------------|
| Characters | 5,751 | 5,751 |
| Audio duration | ~7.68 min (estimated) | 5.44 min (actual) |
| Credits | 5,751 | 5,439 |
| Cost | $1.7253 (wrong) | $1.6317 (correct) |

**Duration-based is 5.4% more accurate** in this case.

The difference varies by content:
- Technical content with code: More characters, less audio time
- Conversational content: Fewer characters, more audio time
- Content with SSML breaks: Longer audio relative to characters

## Migration guide

### For new generations

No action needed - duration-based costs are automatic.

### For existing audio files

Run the regeneration script:

```bash
cd scripts/hogfm
uv run python handbook/regenerate_costs.py
```

This will update all `.cost.json` files with accurate duration-based costs.

### If mutagen fails to install

The system falls back to character-based estimation:

```json
{
  "audio_duration_seconds": null,
  "audio_duration_minutes": null,
  "credits_used": null,
  "estimated_cost_usd": 2.3025,
  "note": "Cost estimated from character count (audio duration not available). Install mutagen or pydub for accurate costs."
}
```

To fix: `uv pip install mutagen`

## Summary

✅ **More accurate costs** - Based on actual audio duration, not estimated from characters  
✅ **Easy to understand** - Direct mapping: 1 minute = $0.30  
✅ **Automatic** - No changes to workflow  
✅ **Backward compatible** - Falls back to character-based if needed  
✅ **Verifiable** - Real data shows $0.277/min average vs expected $0.30/min

Your current 10 audio files:
- **Duration:** 99.76 minutes (1.66 hours)
- **Cost:** $27.66
- **Effective rate:** $0.277/min (matches expected $0.30/min within rounding)
