{"record":{"id":"9b9c16dd85ebc575","repo":"Hmbown/CodeWhale","slug":"unsupported-voice-clone-sample-extension-other","errorCode":null,"errorMessage":"unsupported voice clone sample extension '{other}'. Use .mp3 or .wav.","messagePattern":"unsupported voice clone sample extension '(.+?)'\\. Use \\.mp3 or \\.wav\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/speech.rs","lineNumber":421,"sourceCode":"fn voice_clone_data_uri_from_bytes(path: &Path, bytes: &[u8]) -> anyhow::Result<String> {\n    let base64_audio = general_purpose::STANDARD.encode(bytes);\n    if base64_audio.len() > VOICE_CLONE_BASE64_MAX_BYTES {\n        anyhow::bail!(\n            \"voice clone sample is too large after base64 encoding ({} bytes > 10 MB)\",\n            base64_audio.len()\n        );\n    }\n\n    let extension = path\n        .extension()\n        .and_then(|value| value.to_str())\n        .unwrap_or_default()\n        .to_ascii_lowercase();\n    let mime = match extension.as_str() {\n        \"mp3\" => \"audio/mpeg\",\n        \"wav\" => \"audio/wav\",\n        other => {\n            anyhow::bail!(\"unsupported voice clone sample extension '{other}'. Use .mp3 or .wav.\");\n        }\n    };\n\n    Ok(format!(\"data:{mime};base64,{base64_audio}\"))\n}\n\npub(crate) fn describe_speech_voice(voice: &str) -> String {\n    if voice.starts_with(\"data:\") {\n        \"embedded voice clone sample\".to_string()\n    } else {\n        voice.to_string()\n    }\n}\n\nfn openai_compatible_base_url(base_url: &str) -> String {\n    let trimmed = base_url.trim_end_matches('/');\n    if trimmed.ends_with(\"/v1\") || trimmed.ends_with(\"/beta\") {\n        trimmed.to_string()","sourceCodeStart":403,"sourceCodeEnd":439,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/tools/speech.rs#L403-L439","documentation":"voice_clone_data_uri_from_bytes maps the sample's file extension to a MIME type and supports only .mp3 (audio/mpeg) and .wav (audio/wav). Any other extension, including no extension at all (the extension defaults to empty), is rejected because the provider needs a concrete audio MIME type in the data URI.","triggerScenarios":"Passing .m4a, .ogg, .flac, .opus, .webm, uppercase variants are fine (lowercased) but .MP3 works while .mp4 fails; passing a file with no extension; passing a temporary file whose suffix was stripped.","commonSituations":"iPhone/Android recordings (.m4a, .amr); Audacity exports as .flac or .ogg; voice memos renamed without extension; tempfiles created without suffix.","solutions":["Convert the sample to mp3 or wav before uploading: ffmpeg -i in.m4a -codec:a libmp3lame -b:a 128k sample.mp3.","If the content is already mp3/wav with a wrong suffix, rename the file so the extension matches the actual container.","Ensure tempfile-based flows preserve a .mp3/.wav suffix instead of a random name."],"exampleFix":"# before\nffmpeg -i recording.m4a voice.wav   # then pass voice.wav? no - pass original .m4a -> rejected\n\n# after\nffmpeg -i recording.m4a -codec:a libmp3lame -b:a 128k voice.mp3\n# pass voice.mp3 -> accepted","handlingStrategy":"type-guard","validationCode":"fn sample_extension_supported(path: &Path) -> bool {\n    matches!(\n        path.extension().and_then(|e| e.to_str()).map(|e| e.to_ascii_lowercase()).as_deref(),\n        Some(\"mp3\" | \"wav\")\n    )\n}","typeGuard":"fn is_supported_voice_sample(path: &Path) -> bool {\n    matches!(\n        path.extension().and_then(|e| e.to_str()).map(str::to_ascii_lowercase).as_deref(),\n        Some(\"mp3\" | \"wav\")\n    )\n}","tryCatchPattern":null,"preventionTips":["Convert phone/DAW recordings (m4a, flac, ogg) to mp3 or wav before using them as clone samples.","Preserve the file extension when staging samples in temp dirs.","The check is on the extension only, so make sure the container actually matches the suffix."],"tags":["speech","voice-clone","file-format","mime","rust"],"backgroundTag":"unsupported-file-format","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}