| name | audio-gen |
| description | Audio generation and transcription covering text-to-speech (TTS), speech-to-text (STT/Whisper), audio format conversion, podcast editing workflows, and audio processing. This skill should be used when Cline needs to generate speech from text, transcribe audio recordings, convert between audio formats, or perform audio editing tasks. |
Audio Generation
This skill handles audio generation, transcription, format conversion, and editing. It covers text-to-speech synthesis, speech-to-text transcription with Whisper, format conversion between audio types, and podcast-style editing workflows including segment assembly and normalization.
When to Use This Skill
- Converting text to speech (TTS) for narration or accessibility
- Transcribing audio recordings to text (STT)
- Converting between audio formats (MP3, WAV, OGG, FLAC, AAC, M4A)
- Editing audio: trimming, concatenating, normalizing volume
- Podcast production: assembling segments, adding intro/outro, leveling
- Batch processing audio files (format conversion, normalization)
- Extracting audio from video files
- Generating audio with specific characteristics (sample rate, bitrate, channels)
- Creating audio snippets or ringtone-length clips
What This Skill Does
- Text-to-Speech (TTS): Generate spoken audio from text input
- Speech-to-Text (STT): Transcribe audio to text with timestamps
- Format Conversion: Convert between audio formats with quality control
- Audio Editing: Trim, concatenate, normalize, and process audio
- Podcast Workflows: Assemble episodes from segments with professional polish
Process
Step 1: Text-to-Speech Generation
Generate speech from text using available TTS engines:
import subprocess
from pathlib import Path
def tts_with_edge_tts(text, output_path, voice="en-US-AriaNeural", rate="+0%", pitch="+0Hz"):
"""Generate speech using edge-tts (free, high quality, many voices).
Args:
text: Text content to speak
output_path: Output audio file path (.mp3 or .wav)
voice: Voice name (list with: edge-tts --list-voices)
rate: Speaking rate adjustment (e.g., "+20%", "-10%")
pitch: Pitch adjustment (e.g., "+5Hz", "-10Hz")
"""
cmd = [
"edge-tts",
"--voice", voice,
"--rate", rate,
"--pitch", pitch,
"--text", text,
"--write-media", output_path
]
subprocess.run(cmd, check=True, capture_output=True)
return output_path
def tts_with_piper(text, output_path, model="en_US-lessac-medium", noise_scale=0.667):
"""Generate speech using Piper (local, fast, offline).
Args:
model: Voice model name (browse: https://rhasspy.github.io/piper-samples/)
noise_scale: Controls variation (0.0 = deterministic, 1.0 = varied)
"""
cmd = [
"piper",
"--model", model,
"--noise-scale", str(noise_scale),
"--output_file", output_path
]
subprocess.run(cmd, input=text.encode(), check=True, capture_output=True)
return output_path
Voice selection guide:
| Engine | Best For | Pros | Cons |
|---|
| edge-tts | General narration | Free, many voices, natural | Requires internet |
| Piper | Offline/local use | Fast, no internet, private | Fewer voice options |
| espeak | Quick prototyping | Ubiquitous, tiny | Robotic quality |
| macOS say | Mac users | Built-in | Mac only, basic quality |
Step 2: Speech-to-Text Transcription
import json
def transcribe_with_whisper(audio_path, output_format="txt", model="base", language=None):
"""Transcribe audio using OpenAI Whisper.
Args:
audio_path: Path to audio file
output_format: txt, srt, vtt, json, tsv
model: tiny, base, small, medium, large (quality/speed tradeoff)
language: Language code (e.g., "en") or None for auto-detect
"""
cmd = ["whisper", audio_path, "--output_format", output_format, "--model", model]
if language:
cmd.extend(["--language", language])
subprocess.run(cmd, check=True, capture_output=True)
stem = Path(audio_path).stem
parent = Path(audio_path).parent
output_file = parent / f"{stem}.{output_format}"
if output_format == "json" and output_file.exists():
with open(output_file) as f:
return json.load(f)
elif output_file.exists():
return output_file.read_text()
return None
def transcribe_with_faster_whisper(audio_path, model="base", language=None):
"""Transcribe using faster-whisper (4x faster, less memory).
Returns list of segments with timestamps.
"""
from faster_whisper import WhisperModel
whisper_model = WhisperModel(model, device="cpu", compute_type="int8")
segments, info = whisper_model.transcribe(audio_path, language=language)
results = []
for segment in segments:
results.append({
"start": segment.start,
"end": segment.end,
"text": segment.text.strip()
})
return results
Model selection guide:
| Model | VRAM | Speed | Accuracy | Best For |
|---|
| tiny | ~1 GB | Fastest | Basic | Quick rough drafts |
| base | ~1 GB | Fast | Good | Most use cases |
| small | ~2 GB | Moderate | Better | Interviews, meetings |
| medium | ~5 GB | Slow | Great | Professional transcripts |
| large | ~10 GB | Slowest | Best | Critical accuracy needs |
Step 3: Audio Format Conversion
def convert_audio(input_path, output_path, bitrate="192k", sample_rate=None, channels=None):
"""Convert between audio formats with quality control.
Format detected from output_path extension.
Common: mp3, wav, ogg, flac, aac, m4a, opus
"""
cmd = ["ffmpeg", "-i", input_path, "-y"]
format_codecs = {
".mp3": "libmp3lame",
".ogg": "libvorbis",
".flac": "flac",
".aac": "aac",
".m4a": "aac",
".opus": "libopus",
".wav": None,
}
ext = Path(output_path).suffix.lower()
codec = format_codecs.get(ext)
if codec:
cmd.extend(["-c:a", codec])
if bitrate and ext not in (".wav", ".flac"):
cmd.extend(["-b:a", bitrate])
if sample_rate:
cmd.extend(["-ar", str(sample_rate)])
if channels:
cmd.extend(["-ac", str(channels)])
cmd.append(output_path)
subprocess.run(cmd, check=True, capture_output=True)
return output_path
Format recommendations:
| Format | Use Case | Typical Bitrate |
|---|
| MP3 | Universal compatibility | 128-320 kbps |
| WAV | Lossless editing, archives | 1411 kbps (CD) |
| FLAC | Lossless with compression | ~800 kbps |
| OGG Vorbis | Web, open formats | 128-256 kbps |
| Opus | Best quality/size ratio | 64-128 kbps |
| M4A/AAC | Apple ecosystem | 128-256 kbps |
Step 4: Audio Editing
def trim_audio(input_path, output_path, start_sec=0, end_sec=None):
"""Trim audio to specified time range."""
cmd = ["ffmpeg", "-i", input_path, "-ss", str(start_sec)]
if end_sec:
cmd.extend(["-to", str(end_sec)])
cmd.extend(["-c", "copy", "-y", output_path])
subprocess.run(cmd, check=True, capture_output=True)
return output_path
def concatenate_audio(input_files, output_path, crossfade_sec=0):
"""Concatenate multiple audio files.
Args:
input_files: List of audio file paths
crossfade_sec: Crossfade duration between segments (0 = no crossfade)
"""
if crossfade_sec > 0:
inputs = []
for f in input_files:
inputs.extend(["-i", f])
filter_parts = []
n = len(input_files)
for i in range(n - 1):
if i == 0:
input_a = f"[{i}:a]"
else:
input_a = f"[cf{i-1}]"
input_b = f"[{i+1}:a]"
output_label = f"[cf{i}]"
filter_parts.append(
f"{input_a}{input_b}acrossfade=d={crossfade_sec}:c1=tri:c2=tri{output_label}"
)
cmd = ["ffmpeg"] + inputs + [
"-filter_complex", ";".join(filter_parts),
"-y", output_path
]
else:
list_file = "/tmp/concat_list.txt"
with open(list_file, "w") as f:
for path in input_files:
f.write(f"file '{path}'\n")
cmd = [
"ffmpeg", "-f", "concat", "-safe", "0",
"-i", list_file, "-c", "copy", "-y", output_path
]
subprocess.run(cmd, check=True, capture_output=True)
return output_path
def normalize_audio(input_path, output_path, target_lufs=-16):
"""Normalize audio loudness to broadcast standard.
Args:
target_lufs: Target loudness in LUFS (-14 for Spotify, -16 for podcast, -24 for broadcast)
"""
cmd = [
"ffmpeg", "-i", input_path,
"-af", f"loudnorm=I={target_lufs}:TP=-1.5:LRA=11",
"-y", output_path
]
subprocess.run(cmd, check=True, capture_output=True)
return output_path
Step 5: Podcast Production Workflow
def assemble_podcast(segments, output_path, intro_path=None, outro_path=None,
crossfade=1.5, target_lufs=-16):
"""Assemble a podcast episode from segments with intro/outro and normalization.
Args:
segments: Ordered list of segment audio file paths
intro_path: Optional intro music/audio
outro_path: Optional outro music/audio
crossfade: Crossfade duration between segments in seconds
target_lufs: Target loudness for normalization
"""
all_parts = []
if intro_path:
all_parts.append(intro_path)
all_parts.extend(segments)
if outro_path:
all_parts.append(outro_path)
raw_path = output_path.replace(".mp3", "_raw.mp3")
concatenate_audio(all_parts, raw_path, crossfade_sec=crossfade)
normalize_audio(raw_path, output_path, target_lufs=target_lufs)
Path(raw_path).unlink(missing_ok=True)
return output_path
def extract_audio_from_video(video_path, output_path=None, bitrate="192k"):
"""Extract audio track from a video file."""
if output_path is None:
output_path = f"{Path(video_path).stem}.mp3"
cmd = [
"ffmpeg", "-i", video_path,
"-vn",
"-acodec", "libmp3lame",
"-b:a", bitrate,
"-y", output_path
]
subprocess.run(cmd, check=True, capture_output=True)
return output_path
Error Handling
- Missing ffmpeg: Verify ffmpeg is installed before any operation; provide platform-specific install instructions
- Unsupported format: Check format compatibility; suggest conversion before processing
- Memory errors on long audio: For files >1 hour, process in segments; use streaming where possible
- Transcription quality: If Whisper output is poor, try a larger model or specify language explicitly
- Voice model not found: List available voices/models and suggest alternatives
- Concatenation failures: Ensure all segments share the same sample rate and channel count before concatenating
Common Pitfalls
- ❌ Not normalizing audio — inconsistent loudness is the #1 podcast quality issue
- ❌ Using low-quality TTS voices for professional output — test multiple voices before committing
- ❌ Forgetting
-y flag in ffmpeg — it will hang waiting for overwrite confirmation
- ❌ Using
-c copy for crossfade operations — re-encoding is required for filter effects
- ❌ Not specifying language in Whisper — auto-detection can be wrong for short clips or accented speech
Cline Workflow Notes
- Install location: Copy this skill directory to
.cline/skills/audio-gen/ (project-level) or ~/.cline/skills/audio-gen/ (global)
- Always check for ffmpeg first: Most operations depend on ffmpeg being available
- Preserve originals: Never overwrite source audio files — always write to new paths
- Report audio properties: Show duration, format, bitrate, and sample rate after processing
- Normalize by default: Always normalize loudness for podcast and content production
- Use lossless intermediates: When chaining operations, use WAV for intermediate files to avoid generation loss
- Test with short clips: For long audio, test the workflow on a 30-second clip first
Dependencies
sudo apt install ffmpeg
pip install edge-tts
pip install piper-tts
pip install openai-whisper
pip install faster-whisper
pip install pydub
pip install Pillow