Skip to content

Failed to save combined model #285

Closed

Description

@qianchencc

当我尝试使用这个实例中的程序混合一个声音模型并保存到本地时,问题发生了.
import requests

response = requests.post(
"http://localhost:8880/v1/audio/voices/combine",
json="af_heart(2.2)+af_nicole(2.8)+af_jessica(1.5)",
headers={"Accept": "application/octet-stream"}
)

if response.status_code != 200:
print(f"Error: {response.text}")
exit(1)

with open("custom_voice.pt", "wb") as f:
f.write(response.content)

print("音色文件保存成功")
当json输入如上时,显然af_heart(2.2)会被下面这段相应的代码识别成一个模型,而非匹配权重.因此引发了错误:
Error: {"detail":{"error":"validation_error","message":"Base voice 'af_heart(2.2)' not found. Available voices:...]

@router.post("/audio/voices/combine")
async def combine_voices(request: Union[str, List[str]]):
"""Combine multiple voices into a new voice and return the .pt file.

Args:
    request: Either a string with voices separated by + (e.g. "voice1+voice2")
            or a list of voice names to combine

Returns:
    FileResponse with the combined voice .pt file

Raises:
    HTTPException:
        - 400: Invalid request (wrong number of voices, voice not found)
        - 500: Server error (file system issues, combination failed)
"""
# Check if local voice saving is allowed
if not settings.allow_local_voice_saving:
    raise HTTPException(
        status_code=403,
        detail={
            "error": "permission_denied",
            "message": "Local voice saving is disabled",
            "type": "permission_error",
        },
    )

try:
    # Convert input to list of voices
    if isinstance(request, str):
        # Check if it's an OpenAI voice name
        mapped_voice = _openai_mappings["voices"].get(request)
        if mapped_voice:
            request = mapped_voice
        voices = [v.strip() for v in request.split("+") if v.strip()]
    else:
        # For list input, map each voice if it's an OpenAI voice name
        voices = [_openai_mappings["voices"].get(v, v) for v in request]
        voices = [v.strip() for v in voices if v.strip()]

    if not voices:
        raise ValueError("No voices provided")

    # For multiple voices, validate base voices exist
    tts_service = await get_tts_service()
    available_voices = await tts_service.list_voices()
    for voice in voices:
        if voice not in available_voices:
            raise ValueError(
                f"Base voice '{voice}' not found. Available voices: {', '.join(sorted(available_voices))}"
            )

    # Combine voices
    combined_tensor = await tts_service.combine_voices(voices=voices)
    combined_name = "+".join(voices)

    # Save to temp file
    temp_dir = tempfile.gettempdir()
    voice_path = os.path.join(temp_dir, f"{combined_name}.pt")
    buffer = io.BytesIO()
    torch.save(combined_tensor, buffer)
    async with aiofiles.open(voice_path, "wb") as f:
        await f.write(buffer.getvalue())

    return FileResponse(
        voice_path,
        media_type="application/octet-stream",
        filename=f"{combined_name}.pt",
        headers={
            "Content-Disposition": f"attachment; filename={combined_name}.pt",
            "Cache-Control": "no-cache",
        },
    )

except ValueError as e:
    logger.warning(f"Invalid voice combination request: {str(e)}")
    raise HTTPException(
        status_code=400,
        detail={
            "error": "validation_error",
            "message": str(e),
            "type": "invalid_request_error",
        },
    )
except RuntimeError as e:
    logger.error(f"Voice combination processing error: {str(e)}")
    raise HTTPException(
        status_code=500,
        detail={
            "error": "processing_error",
            "message": "Failed to process voice combination request",
            "type": "server_error",
        },
    )
except Exception as e:
    logger.error(f"Unexpected error in voice combination: {str(e)}")
    raise HTTPException(
        status_code=500,
        detail={
            "error": "server_error",
            "message": "An unexpected error occurred",
            "type": "server_error",
        },
    )

也许这个功能的实现代码以及README.md需要修改,如果我应该尝试别的方法,请回复我.
感谢这个项目,为我提供了很多便利!

Activity

  1. added
    bugSomething isn't working
    backendProblems with the Kokoro-FastAPI backend
    on Apr 9, 2025
  2. BobSoft3000 commented on Mar 18, 2026

    @BobSoft3000

    The combine function doesn't appear to accept parentheses (unlike using the 'speech' API with a custom combination), So the example in the README.md doesn't currently work:

    # Example 3: Download combined voice as .pt file
    response = requests.post(
        "http://localhost:8880/v1/audio/voices/combine",
        json="af_bella(2)+af_sky(1)"  # 2:1 ratio = 67%/33%
    )

    A workaround is to remove the parentheses and duplicate the models to get the same mix:

    response = requests.post(
        "http://localhost:8880/v1/audio/voices/combine",
        json="af_bella+af_bella+af_sky"  # 2:1 ratio = 67%/33%
    )

    If its not viable to support specifying the model weightings directly, is it possible to update the documentation in the meantime?

  3. Jakeshadow commented on Jun 13, 2026

    @Jakeshadow
  4. added a commit that references this issue on Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendProblems with the Kokoro-FastAPI backendbugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions