2024-02-13 15:11:53 +01:00
|
|
|
import os
|
2024-03-21 00:11:36 +01:00
|
|
|
import logging
|
2024-02-11 09:17:50 +01:00
|
|
|
from fastapi import (
|
|
|
|
FastAPI,
|
|
|
|
Request,
|
|
|
|
Depends,
|
|
|
|
HTTPException,
|
|
|
|
status,
|
|
|
|
UploadFile,
|
|
|
|
File,
|
|
|
|
Form,
|
|
|
|
)
|
2024-04-20 22:15:59 +02:00
|
|
|
|
|
|
|
from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
|
|
|
|
|
2024-02-11 09:17:50 +01:00
|
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
from faster_whisper import WhisperModel
|
2024-04-20 22:21:52 +02:00
|
|
|
from pydantic import BaseModel
|
|
|
|
|
2024-02-11 09:17:50 +01:00
|
|
|
|
2024-04-20 22:15:59 +02:00
|
|
|
import requests
|
|
|
|
import hashlib
|
|
|
|
from pathlib import Path
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
2024-02-11 09:17:50 +01:00
|
|
|
from constants import ERROR_MESSAGES
|
|
|
|
from utils.utils import (
|
|
|
|
decode_token,
|
|
|
|
get_current_user,
|
|
|
|
get_verified_user,
|
|
|
|
get_admin_user,
|
|
|
|
)
|
|
|
|
from utils.misc import calculate_sha256
|
|
|
|
|
2024-03-31 10:13:39 +02:00
|
|
|
from config import (
|
|
|
|
SRC_LOG_LEVELS,
|
|
|
|
CACHE_DIR,
|
|
|
|
UPLOAD_DIR,
|
|
|
|
WHISPER_MODEL,
|
|
|
|
WHISPER_MODEL_DIR,
|
2024-04-11 04:30:00 +02:00
|
|
|
WHISPER_MODEL_AUTO_UPDATE,
|
2024-04-02 14:47:52 +02:00
|
|
|
DEVICE_TYPE,
|
2024-04-20 23:04:16 +02:00
|
|
|
AUDIO_OPENAI_API_BASE_URL,
|
|
|
|
AUDIO_OPENAI_API_KEY,
|
2024-03-31 10:13:39 +02:00
|
|
|
)
|
2024-03-21 00:11:36 +01:00
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
log.setLevel(SRC_LOG_LEVELS["AUDIO"])
|
2024-02-11 09:17:50 +01:00
|
|
|
|
|
|
|
app = FastAPI()
|
|
|
|
app.add_middleware(
|
|
|
|
CORSMiddleware,
|
|
|
|
allow_origins=["*"],
|
|
|
|
allow_credentials=True,
|
|
|
|
allow_methods=["*"],
|
|
|
|
allow_headers=["*"],
|
|
|
|
)
|
|
|
|
|
2024-04-20 22:15:59 +02:00
|
|
|
|
2024-04-20 23:04:16 +02:00
|
|
|
app.state.OPENAI_API_BASE_URL = AUDIO_OPENAI_API_BASE_URL
|
|
|
|
app.state.OPENAI_API_KEY = AUDIO_OPENAI_API_KEY
|
2024-04-20 22:15:59 +02:00
|
|
|
|
2024-04-02 14:47:52 +02:00
|
|
|
# setting device type for whisper model
|
|
|
|
whisper_device_type = DEVICE_TYPE if DEVICE_TYPE and DEVICE_TYPE == "cuda" else "cpu"
|
|
|
|
log.info(f"whisper_device_type: {whisper_device_type}")
|
|
|
|
|
2024-04-20 22:15:59 +02:00
|
|
|
SPEECH_CACHE_DIR = Path(CACHE_DIR).joinpath("./audio/speech/")
|
|
|
|
SPEECH_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
|
2024-04-20 22:21:52 +02:00
|
|
|
class OpenAIConfigUpdateForm(BaseModel):
|
|
|
|
url: str
|
|
|
|
key: str
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/config")
|
|
|
|
async def get_openai_config(user=Depends(get_admin_user)):
|
|
|
|
return {
|
|
|
|
"OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL,
|
|
|
|
"OPENAI_API_KEY": app.state.OPENAI_API_KEY,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@app.post("/config/update")
|
|
|
|
async def update_openai_config(
|
|
|
|
form_data: OpenAIConfigUpdateForm, user=Depends(get_admin_user)
|
|
|
|
):
|
|
|
|
if form_data.key == "":
|
|
|
|
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.API_KEY_NOT_FOUND)
|
|
|
|
|
|
|
|
app.state.OPENAI_API_BASE_URL = form_data.url
|
|
|
|
app.state.OPENAI_API_KEY = form_data.key
|
|
|
|
|
|
|
|
return {
|
|
|
|
"status": True,
|
|
|
|
"OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL,
|
|
|
|
"OPENAI_API_KEY": app.state.OPENAI_API_KEY,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-04-20 22:15:59 +02:00
|
|
|
@app.post("/speech")
|
|
|
|
async def speech(request: Request, user=Depends(get_verified_user)):
|
2024-04-20 23:00:24 +02:00
|
|
|
body = await request.body()
|
|
|
|
name = hashlib.sha256(body).hexdigest()
|
|
|
|
|
|
|
|
file_path = SPEECH_CACHE_DIR.joinpath(f"{name}.mp3")
|
|
|
|
file_body_path = SPEECH_CACHE_DIR.joinpath(f"{name}.json")
|
|
|
|
|
|
|
|
# Check if the file already exists in the cache
|
|
|
|
if file_path.is_file():
|
|
|
|
return FileResponse(file_path)
|
|
|
|
|
|
|
|
headers = {}
|
|
|
|
headers["Authorization"] = f"Bearer {app.state.OPENAI_API_KEY}"
|
|
|
|
headers["Content-Type"] = "application/json"
|
|
|
|
|
|
|
|
r = None
|
2024-04-20 22:15:59 +02:00
|
|
|
try:
|
2024-04-20 23:00:24 +02:00
|
|
|
r = requests.post(
|
|
|
|
url=f"{app.state.OPENAI_API_BASE_URL}/audio/speech",
|
|
|
|
data=body,
|
|
|
|
headers=headers,
|
|
|
|
stream=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
r.raise_for_status()
|
|
|
|
|
|
|
|
# Save the streaming content to a file
|
|
|
|
with open(file_path, "wb") as f:
|
|
|
|
for chunk in r.iter_content(chunk_size=8192):
|
|
|
|
f.write(chunk)
|
|
|
|
|
|
|
|
with open(file_body_path, "w") as f:
|
|
|
|
json.dump(json.loads(body.decode("utf-8")), f)
|
|
|
|
|
|
|
|
# Return the saved file
|
|
|
|
return FileResponse(file_path)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
log.exception(e)
|
|
|
|
error_detail = "Open WebUI: Server Connection Error"
|
|
|
|
if r is not None:
|
|
|
|
try:
|
|
|
|
res = r.json()
|
|
|
|
if "error" in res:
|
|
|
|
error_detail = f"External: {res['error']['message']}"
|
|
|
|
except:
|
|
|
|
error_detail = f"External: {e}"
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=r.status_code if r != None else 500,
|
|
|
|
detail=error_detail,
|
|
|
|
)
|
2024-04-20 22:15:59 +02:00
|
|
|
|
2024-02-11 09:17:50 +01:00
|
|
|
|
2024-04-20 22:15:59 +02:00
|
|
|
@app.post("/transcriptions")
|
2024-02-11 09:17:50 +01:00
|
|
|
def transcribe(
|
|
|
|
file: UploadFile = File(...),
|
|
|
|
user=Depends(get_current_user),
|
|
|
|
):
|
2024-03-21 00:11:36 +01:00
|
|
|
log.info(f"file.content_type: {file.content_type}")
|
2024-02-11 09:17:50 +01:00
|
|
|
|
|
|
|
if file.content_type not in ["audio/mpeg", "audio/wav"]:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
|
|
detail=ERROR_MESSAGES.FILE_NOT_SUPPORTED,
|
|
|
|
)
|
|
|
|
|
|
|
|
try:
|
|
|
|
filename = file.filename
|
|
|
|
file_path = f"{UPLOAD_DIR}/{filename}"
|
|
|
|
contents = file.file.read()
|
|
|
|
with open(file_path, "wb") as f:
|
|
|
|
f.write(contents)
|
|
|
|
f.close()
|
|
|
|
|
2024-04-11 04:30:00 +02:00
|
|
|
whisper_kwargs = {
|
|
|
|
"model_size_or_path": WHISPER_MODEL,
|
|
|
|
"device": whisper_device_type,
|
|
|
|
"compute_type": "int8",
|
|
|
|
"download_root": WHISPER_MODEL_DIR,
|
|
|
|
"local_files_only": not WHISPER_MODEL_AUTO_UPDATE,
|
|
|
|
}
|
|
|
|
|
|
|
|
log.debug(f"whisper_kwargs: {whisper_kwargs}")
|
|
|
|
|
2024-04-11 04:44:44 +02:00
|
|
|
try:
|
2024-04-11 04:30:00 +02:00
|
|
|
model = WhisperModel(**whisper_kwargs)
|
|
|
|
except:
|
2024-04-13 11:18:13 +02:00
|
|
|
log.warning(
|
2024-04-11 04:44:44 +02:00
|
|
|
"WhisperModel initialization failed, attempting download with local_files_only=False"
|
|
|
|
)
|
2024-04-11 04:30:00 +02:00
|
|
|
whisper_kwargs["local_files_only"] = False
|
|
|
|
model = WhisperModel(**whisper_kwargs)
|
2024-02-11 09:17:50 +01:00
|
|
|
|
|
|
|
segments, info = model.transcribe(file_path, beam_size=5)
|
2024-03-21 00:11:36 +01:00
|
|
|
log.info(
|
2024-02-11 09:17:50 +01:00
|
|
|
"Detected language '%s' with probability %f"
|
|
|
|
% (info.language, info.language_probability)
|
|
|
|
)
|
|
|
|
|
|
|
|
transcript = "".join([segment.text for segment in list(segments)])
|
|
|
|
|
2024-02-11 11:17:24 +01:00
|
|
|
return {"text": transcript.strip()}
|
2024-02-11 09:17:50 +01:00
|
|
|
|
|
|
|
except Exception as e:
|
2024-03-21 00:11:36 +01:00
|
|
|
log.exception(e)
|
2024-02-11 09:17:50 +01:00
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
|
|
detail=ERROR_MESSAGES.DEFAULT(e),
|
|
|
|
)
|