Skip to content

bleachemd/Transcribify

Repository files navigation

transcribify

a minimal async audio transcription service. upload an audio file, get back a full transcript and a structured json report — all within a 30-minute per-user limit.


project structure

transcribify/
├── app/
│   ├── __init__.py
│   ├── config.py        — env-based settings via pydantic-settings
│   ├── database.py      — sqlalchemy engine, session factory, base
│   ├── models.py        — user and job orm models
│   ├── auth.py          — jwt creation, bcrypt verification, get_current_user dep
│   ├── providers.py     — abstract interfaces + groq whisper and openrouter impls
│   └── main.py          — fastapi app, routes, background task
├── tests/
│   ├── __init__.py
│   └── test_main.py     — pytest suite (all mocked, no real api calls)
├── .env.example
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── README.md

api endpoints

method path description auth required
POST /auth/register register a new user
POST /auth/login get a jwt token
POST /jobs upload audio for transcription
GET /jobs/:id get job status / result
GET /me/usage check your audio usage

job lifecycle

pendingprocessingdone / failed

the job response when done looks like this:

{
  "id": 1,
  "status": "done",
  "filename": "meeting.mp3",
  "duration_seconds": 182.5,
  "transcript": "hello everyone, welcome to the weekly sync...",
  "report": {
    "summary": "weekly team sync discussing q2 targets and product roadmap",
    "key_points": ["q2 revenue target increased by 15%", "new feature launch scheduled for july"],
    "topics": ["revenue", "product roadmap", "team updates"],
    "sentiment": "positive"
  },
  "error": null,
  "created_at": "2024-01-15T10:30:00"
}

getting started

via docker (recommended)

requires docker and docker compose.

# 1. clone and enter the repo
git clone <repo-url>
cd transcribify

# 2. create your env file
cp .env.example .env
# edit .env — set SECRET_KEY, GROQ_API_KEY, OPENROUTER_API_KEY

# 3. create a data directory for the sqlite db
mkdir -p data

# 4. build and start
docker compose up --build

the service will be available at http://localhost:8000. interactive docs: http://localhost:8000/docs.


local setup

requires python 3.11+ and ffmpeg installed on your system.

# install ffmpeg (ubuntu/debian)
sudo apt-get install ffmpeg

# install ffmpeg (macos)
brew install ffmpeg

# create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate  # on windows: .venv\Scripts\activate

# install dependencies
pip install -r requirements.txt

# create your env file
cp .env.example .env
# edit .env and fill in your keys

# start the server
uvicorn app.main:app --reload

running tests

tests use a separate sqlite db and mock all external calls — no api keys or internet needed.

pytest tests/ -v

environment variables

variable description default
SECRET_KEY jwt signing secret change-me-in-production
GROQ_API_KEY groq api key for whisper transcription
OPENROUTER_API_KEY openrouter api key for llm report
DATABASE_URL sqlalchemy sqlite connection string sqlite:///./transcribify.db
AUDIO_LIMIT_SECONDS per-user audio limit in seconds 1800 (30 min)
ACCESS_TOKEN_EXPIRE_MINUTES jwt token lifetime 1440 (24 h)

обоснование выбора api

для транскрипции я выбрала groq whisper, потому что он полностью бесплатный, работает невероятно быстро и отлично справляется с распознаванием. для llm я использовала openrouter (модель gemma 4 31b от google), так как это дает бесплатный доступ к очень мощной модели, которая стабильно возвращает валидный и строго структурированный json, что критически важно для нашей задачи.


как я работала с ai

базовые роуты и структуру бд я сгенерировала через ии. в процессе я заметила критическую ошибку: ассистент предложил использовать синхронную библиотеку requests для отправки аудиофайла провайдеру транскрипции прямо внутри асинхронного эндпоинта fastapi. при реальной нагрузке это полностью заблокировало бы event loop. я исправила это, переписав все внешние api-вызовы на aiohttp, чтобы сервис оставался по-настоящему асинхронным. также пришлось руками переписывать фикстуры в pytest, так как ии пытался стучаться в реальные api вместо корректного использования unittest.mock.patch.


демо

полная последовательность запросов через curl:

# 1. регистрация
curl -s -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "password": "secret123"}'

# ответ:
# {"id": 1, "email": "user@example.com"}


# 2. логин — получаем токен
curl -s -X POST http://localhost:8000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "password": "secret123"}'

# ответ:
# {"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "bearer"}

# сохраняем токен в переменную
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."


# 3. загрузка аудиофайла
curl -s -X POST http://localhost:8000/jobs \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@meeting.mp3"

# ответ:
# {"id": 1, "status": "pending", "filename": "meeting.mp3", "duration_seconds": 182.5}


# 4. проверка статуса (сразу — ещё обрабатывается)
curl -s http://localhost:8000/jobs/1 \
  -H "Authorization: Bearer $TOKEN"

# ответ:
# {"id": 1, "status": "processing", "filename": "meeting.mp3", "transcript": null, "report": null, ...}


# через несколько секунд — джоба готова
curl -s http://localhost:8000/jobs/1 \
  -H "Authorization: Bearer $TOKEN"

# ответ:
# {
#   "id": 1,
#   "status": "done",
#   "filename": "meeting.mp3",
#   "duration_seconds": 182.5,
#   "transcript": "hello everyone, let's kick off our weekly sync...",
#   "report": {
#     "summary": "weekly team sync covering q2 targets and upcoming product release",
#     "key_points": ["q2 revenue target increased by 15%", "new feature launch in july"],
#     "topics": ["revenue", "product roadmap", "team updates"],
#     "sentiment": "positive"
#   },
#   "error": null,
#   "created_at": "2024-06-07T10:30:00"
# }


# 5. проверка использованного лимита
curl -s http://localhost:8000/me/usage \
  -H "Authorization: Bearer $TOKEN"

# ответ:
# {
#   "used_seconds": 182.5,
#   "limit_seconds": 1800,
#   "remaining_seconds": 1617.5,
#   "used_minutes": 3.04,
#   "limit_minutes": 30.0
# }


# 6. что будет при превышении лимита
curl -s -X POST http://localhost:8000/jobs \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@very_long_audio.mp3"

# ответ (если файл не помещается в остаток):
# {"detail": "audio limit exceeded. you have 60s remaining out of 1800s total"}

About

Mini Transcribe Bot

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors