jamiepine/voicebox · error · ValueError

Invalid sample filename: {filename} (must be .wav)

Error message

Invalid sample filename: {filename} (must be .wav)

What it means

Each key in samples.json must end with '.wav' (case-sensitive endswith check). The importer extracts each referenced file to a temp .wav and passes it to add_profile_sample; a non-wav key is rejected before extraction.

Source

Thrown at backend/services/export_import.py:207

                    # Extract to temporary file
                    import tempfile
                    with tempfile.NamedTemporaryFile(suffix=Path(avatar_file).suffix, delete=False) as tmp:
                        tmp.write(zip_file.read(avatar_file))
                        tmp_path = tmp.name

                    try:
                        from .profiles import upload_avatar
                        await upload_avatar(profile.id, tmp_path, db)
                    finally:
                        Path(tmp_path).unlink(missing_ok=True)
                except Exception as e:
                    # Avatar import is optional - continue even if it fails
                    pass

            for filename, reference_text in samples_data.items():
                # Validate filename
                if not filename.endswith('.wav'):
                    raise ValueError(f"Invalid sample filename: {filename} (must be .wav)")
                
                # Extract audio file to temp location
                zip_path = f"samples/{filename}"
                
                if zip_path not in namelist:
                    raise ValueError(f"Sample file not found in ZIP: {zip_path}")
                
                # Extract to temporary file
                import tempfile
                with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
                    tmp.write(zip_file.read(zip_path))
                    tmp_path = tmp.name
                
                try:
                    # Add sample to profile
                    await add_profile_sample(
                        profile.id,
                        tmp_path,

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Convert source audio to .wav and re-export.
  2. If only the extension differs, rename keys (and the matching zip entries) to lowercase .wav.
  3. Ensure the exporter only ever emits .wav.

Example fix

// before
{"clip.mp3": "hello"}

// after
{"clip.wav": "hello"}
Defensive patterns

Strategy: validation

Validate before calling

import io, json, zipfile

def all_keys_wav(file_bytes) -> bool:
    with zipfile.ZipFile(io.BytesIO(file_bytes)) as z:
        return all(k.endswith(".wav") for k in json.loads(z.read("samples.json")))

Type guard

def is_wav_filename(name) -> bool:
    return isinstance(name, str) and name.lower().endswith(".wav")

Try / catch

try:
    await import_profile_from_zip(file_bytes, db)
except ValueError as e:
    if "must be .wav" in str(e):
        # convert audio to wav, rename keys + zip entries, retry
        ...
    raise

Prevention

When it happens

Trigger: samples.json contains a .mp3/.flac/.txt key or an extensionless key; case mismatch such as '.WAV' (uppercase) failing the case-sensitive endswith('.wav') check.

Common situations: Mixing audio formats in a hand-built archive; uppercase extension coming from a case-insensitive filesystem export (macOS/Windows).

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/724f3b6896f3608f. Report an issue: GitHub.