add a simple python fastapi server to serve whisper models for speech-to-text and piper for text-to-speech
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
temporary_audio/
|
||||||
|
whisper_models/
|
||||||
|
piper_models/
|
||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
Generated
+1
@@ -0,0 +1 @@
|
|||||||
|
audio_server
|
||||||
Generated
+11
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.14 (audio_server)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="IncorrectFormatting" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredErrors">
|
||||||
|
<list>
|
||||||
|
<option value="N803" />
|
||||||
|
<option value="N806" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="ignoredBaseClasses">
|
||||||
|
<list>
|
||||||
|
<option value="unittest.TestCase" />
|
||||||
|
<option value="unittest.case.TestCase" />
|
||||||
|
<option value="todo_extract.Task" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="TodoComment" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
Generated
+7
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.14 (transcription_server)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.14 (audio_server)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/audio_server.iml" filepath="$PROJECT_DIR$/.idea/audio_server.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
FROM python:3.14.4-slim AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY pyproject.toml requirements.txt ./
|
||||||
|
COPY src src
|
||||||
|
|
||||||
|
RUN pip wheel --no-cache-dir --no-deps --wheel-dir wheels .
|
||||||
|
|
||||||
|
FROM python:3.14.4-slim AS runner
|
||||||
|
|
||||||
|
COPY --from=builder /app/wheels /wheels
|
||||||
|
RUN pip install --no-cache /wheels/* && rm -rf /wheels
|
||||||
|
RUN apt update
|
||||||
|
RUN apt -y install ffmpeg
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
CMD ["uvicorn", "audio_server:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=82.0.1", "wheel"]
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "audio_server"
|
||||||
|
description = "a fastapi server serving whisper and piper"
|
||||||
|
version = "1.0.0"
|
||||||
|
authors = [
|
||||||
|
{ name = 'Milan', email = 'milan.boemer@gmail.com' }
|
||||||
|
]
|
||||||
|
|
||||||
|
requires-python = '>=3.12'
|
||||||
|
|
||||||
|
dynamic = ["dependencies"]
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
dependencies = { file = ["requirements.txt"] }
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ['src']
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
openai-whisper==20250625
|
||||||
|
fastapi~=0.135.3
|
||||||
|
psutil~=7.2.2
|
||||||
|
uvicorn~=0.44.0
|
||||||
|
starlette~=1.0.0
|
||||||
|
python-multipart~=0.0.24
|
||||||
|
aiofiles~=25.1.0
|
||||||
|
piper-tts~=1.4.2
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
from wave import Wave_write
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
|
from piper import PiperVoice, SynthesisConfig
|
||||||
|
from piper.download_voices import download_voice
|
||||||
|
import psutil
|
||||||
|
from fastapi import FastAPI, UploadFile
|
||||||
|
import whisper
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from fastapi.responses import FileResponse, JSONResponse
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
whisper_model_name = os.getenv("WHISPER_MODEL", default="small")
|
||||||
|
whisper_path = Path("whisper_models")
|
||||||
|
whisper_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
whisper_model = whisper.load_model(whisper_model_name, download_root=whisper_path.as_posix())
|
||||||
|
|
||||||
|
piper_model_name = os.getenv("PIPER_MODEL", default="de_DE-karlsson-low")
|
||||||
|
piper_path = Path("piper_models")
|
||||||
|
piper_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
download_voice(piper_model_name, piper_path)
|
||||||
|
|
||||||
|
temporary_audio_path = Path("temporary_audio")
|
||||||
|
temporary_audio_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def index():
|
||||||
|
return {
|
||||||
|
"memory_usage": psutil.Process(os.getpid()).memory_info().rss,
|
||||||
|
"piper_model": piper_model_name,
|
||||||
|
"whisper_model": whisper_model_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get("/transcribe")
|
||||||
|
async def transcribe(audio_file: UploadFile):
|
||||||
|
file_extensions = {
|
||||||
|
"audio/wave": "wav",
|
||||||
|
"audio/mpeg": "mp3",
|
||||||
|
}
|
||||||
|
|
||||||
|
if audio_file.content_type not in file_extensions.keys():
|
||||||
|
logger.error("%s is not supported", audio_file.content_type)
|
||||||
|
return JSONResponse({"detail": "wrong file type"}, status_code=400)
|
||||||
|
|
||||||
|
file_path = temporary_audio_path / f"{uuid.uuid4()}.{file_extensions[audio_file.content_type]}"
|
||||||
|
|
||||||
|
async with aiofiles.open(file_path, "wb") as f:
|
||||||
|
content = await audio_file.read()
|
||||||
|
await f.write(content)
|
||||||
|
|
||||||
|
# noinspection PyArgumentList
|
||||||
|
transcription = whisper_model.transcribe(file_path.as_posix())
|
||||||
|
|
||||||
|
return JSONResponse(transcription)
|
||||||
|
|
||||||
|
class VoiceRequest(BaseModel):
|
||||||
|
text: str
|
||||||
|
config: SynthesisConfig | None
|
||||||
|
|
||||||
|
@app.get("/tts")
|
||||||
|
async def tts(voice_request: VoiceRequest):
|
||||||
|
voice = PiperVoice.load(piper_path / f"{piper_model_name}.onnx")
|
||||||
|
|
||||||
|
audio_file_path = temporary_audio_path / f"{uuid.uuid4()}.wav"
|
||||||
|
|
||||||
|
with Wave_write(audio_file_path.as_posix()) as writer:
|
||||||
|
voice.synthesize_wav(voice_request.text, writer, syn_config=voice_request.config)
|
||||||
|
|
||||||
|
return FileResponse(audio_file_path, media_type="audio/wav")
|
||||||
Reference in New Issue
Block a user