diff --git a/audio_server/.gitignore b/audio_server/.gitignore
new file mode 100644
index 0000000..aa09c5e
--- /dev/null
+++ b/audio_server/.gitignore
@@ -0,0 +1,3 @@
+temporary_audio/
+whisper_models/
+piper_models/
\ No newline at end of file
diff --git a/audio_server/.idea/.gitignore b/audio_server/.idea/.gitignore
new file mode 100644
index 0000000..b58b603
--- /dev/null
+++ b/audio_server/.idea/.gitignore
@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/audio_server/.idea/.name b/audio_server/.idea/.name
new file mode 100644
index 0000000..f71cdab
--- /dev/null
+++ b/audio_server/.idea/.name
@@ -0,0 +1 @@
+audio_server
\ No newline at end of file
diff --git a/audio_server/.idea/audio_server.iml b/audio_server/.idea/audio_server.iml
new file mode 100644
index 0000000..847b460
--- /dev/null
+++ b/audio_server/.idea/audio_server.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/audio_server/.idea/inspectionProfiles/Project_Default.xml b/audio_server/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..7898187
--- /dev/null
+++ b/audio_server/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/audio_server/.idea/inspectionProfiles/profiles_settings.xml b/audio_server/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/audio_server/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/audio_server/.idea/misc.xml b/audio_server/.idea/misc.xml
new file mode 100644
index 0000000..1b83d63
--- /dev/null
+++ b/audio_server/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/audio_server/.idea/modules.xml b/audio_server/.idea/modules.xml
new file mode 100644
index 0000000..e934ccb
--- /dev/null
+++ b/audio_server/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/audio_server/.idea/vcs.xml b/audio_server/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/audio_server/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/audio_server/Containerfile b/audio_server/Containerfile
new file mode 100644
index 0000000..eae642a
--- /dev/null
+++ b/audio_server/Containerfile
@@ -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"]
\ No newline at end of file
diff --git a/audio_server/pyproject.toml b/audio_server/pyproject.toml
new file mode 100644
index 0000000..c6568cb
--- /dev/null
+++ b/audio_server/pyproject.toml
@@ -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']
\ No newline at end of file
diff --git a/audio_server/requirements.txt b/audio_server/requirements.txt
new file mode 100644
index 0000000..99d547f
--- /dev/null
+++ b/audio_server/requirements.txt
@@ -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
\ No newline at end of file
diff --git a/audio_server/src/audio_server/__init__.py b/audio_server/src/audio_server/__init__.py
new file mode 100644
index 0000000..67d4590
--- /dev/null
+++ b/audio_server/src/audio_server/__init__.py
@@ -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")
\ No newline at end of file