Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dependencies = [
"fastapi",
"pydantic",
"python-multipart",
"edge-tts",
]

[project.urls]
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ loguru
uvicorn
fastapi
pydantic
python-multipart
python-multipart
edge-tts
46 changes: 46 additions & 0 deletions rvc_python/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from fastapi.responses import Response, JSONResponse
from loguru import logger
from pydantic import BaseModel
import edge_tts
import tempfile
import base64
import shutil
Expand All @@ -21,6 +22,13 @@ class SetParamsRequest(BaseModel):
class SetModelsDirRequest(BaseModel):
models_dir: str

class TTSRequest(BaseModel):
text: str
voice: str | None = "Microsoft Server Speech Text to "
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Complete the default value for voice in TTSRequest

The default value for voice appears incomplete: "Microsoft Server Speech Text to ". Please provide a complete and valid default voice identifier to ensure the TTS service functions correctly.

Apply this diff to fix the default voice value:

-    voice: str | None = "Microsoft Server Speech Text to "
+    voice: str | None = "en-US-AriaNeural"
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
voice: str | None = "Microsoft Server Speech Text to "
voice: str | None = "en-US-AriaNeural"

rate: str | None = "+0%"
volume: str | None = "+0%"
pitch: str | None = "+0Hz"

def setup_routes(app: FastAPI):
@app.post("/convert")
def rvc_convert(request: ConvertAudioRequest):
Expand Down Expand Up @@ -117,6 +125,44 @@ def set_models_dir(request: SetModelsDirRequest):
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))

@app.get("/voices")
async def list_voices():
return JSONResponse(content={"voices": await edge_tts.list_voices()})
Comment on lines +128 to +130
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add exception handling to the /voices endpoint

Currently, the list_voices endpoint does not handle exceptions that may occur when calling edge_tts.list_voices(). This could lead to unhandled exceptions and a 500 Internal Server Error response without meaningful information to the client. Consider adding error handling to provide a clear error message.

Apply this diff to incorporate exception handling:

 @app.get("/voices")
 async def list_voices():
+    try:
         voices = await edge_tts.list_voices()
         return JSONResponse(content={"voices": voices})
+    except Exception as e:
+        logger.error(f"Error retrieving voices: {e}")
+        raise HTTPException(status_code=500, detail="Failed to retrieve voices.") from e
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@app.get("/voices")
async def list_voices():
return JSONResponse(content={"voices": await edge_tts.list_voices()})
@app.get("/voices")
async def list_voices():
try:
voices = await edge_tts.list_voices()
return JSONResponse(content={"voices": voices})
except Exception as e:
logger.error(f"Error retrieving voices: {e}")
raise HTTPException(status_code=500, detail="Failed to retrieve voices.") from e


@app.post("/tts")
async def tts(request: TTSRequest):
if not app.state.rvc.current_model:
raise HTTPException(status_code=400, detail="No model loaded. Please load a model first.")

tmp_input = tempfile.NamedTemporaryFile(delete=False, suffix=".wav")
tmp_output = tempfile.NamedTemporaryFile(delete=False, suffix=".wav")
try:
logger.info("Received request to generate audio by tts")
input_path = tmp_input.name
output_path = tmp_output.name

communicate = edge_tts.Communicate(
text=request.text,
voice=request.voice,
rate=request.rate,
volume=request.volume,
pitch=request.pitch
)
await communicate.save(input_path)

app.state.rvc.infer_file(input_path, output_path)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid blocking the event loop with synchronous infer_file method

The app.state.rvc.infer_file method appears to be a synchronous and potentially long-running operation. Executing it directly in an async endpoint could block the event loop, affecting the application's performance. Consider running it in a separate thread using asyncio.to_thread.

Apply this diff to run infer_file without blocking:

-            app.state.rvc.infer_file(input_path, output_path)
+            await asyncio.to_thread(app.state.rvc.infer_file, input_path, output_path)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.state.rvc.infer_file(input_path, output_path)
await asyncio.to_thread(app.state.rvc.infer_file, input_path, output_path)


output_data = tmp_output.read()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reset the file pointer before reading tmp_output

After writing to tmp_output, the file pointer is at the end of the file. Before reading from it, you need to reset the file pointer to the beginning using tmp_output.seek(0); otherwise, output_data may be empty.

Apply this diff to reset the file pointer:

             tmp_output.close()
+            tmp_output = open(output_path, 'rb')
+            output_data = tmp_output.read()
+            tmp_output.close()

Alternatively, seek to the beginning before reading:

             tmp_output.seek(0)
             output_data = tmp_output.read()
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
output_data = tmp_output.read()
tmp_output.seek(0)
output_data = tmp_output.read()

return Response(content=output_data, media_type="audio/wav")
except Exception as e:
logger.error(e)
raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chain exceptions when raising HTTPException

When raising a new exception within an except block, it's good practice to chain it using from e to preserve the original traceback. This helps in distinguishing exceptions in error handling from those in the main code.

Apply this diff to chain exceptions:

             logger.error(e)
-            raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
+            raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}") from e
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}") from e
Tools
Ruff

159-159: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

finally:
tmp_input.close()
tmp_output.close()
os.unlink(tmp_input.name)
os.unlink(tmp_output.name)

def create_app():
app = FastAPI()

Expand Down