Files
transcriber/internal/contract/contract.go
T
av 8f7c3a057a удалён вход Telegram, владелец записи стал обязателен в схеме
- убраны клиент бота, транспорт обновлений, отправитель сообщений, сборка
  входа при старте, секция настроек и зависимость go-telegram-bot-api; из
  конвейера ушла доставка ответа отправителю — исход виден опросом готовности.
  Колонки адресата и значение источника остались в схеме: применённые шаги не
  переписываются
- шаг 202608140003 запрещает пустого владельца у аудиозаписи и у файла;
  существующие строки он не проверяет, и это принято сознательно — искать их
  надо запросом до выкладки
- ревью нашло два пред-существующих дефекта, оба закрыты: пустой второй ответ
  распознавателя стирал сохранённую расшифровку, а пустая расшифровка перестала
  быть заметной вместе с убранной доставкой. Попутно поднят golang.org/x/image
  до v0.45.0 — красный шаг vulns, воспроизводился и на чистом master
2026-08-15 07:24:35 +03:00

61 lines
3.9 KiB
Go

package contract
import (
"context"
"io"
"git.vakhrushev.me/av/transcriber/internal/entity"
)
type AudioInfo struct {
Seconds int // Длина аудиофайла в секундах
}
// Контекст первым доводом несут все интерфейсы, за которыми стоит внешний
// собеседник — процесс `ffmpeg`, S3, SpeechKit. Он здесь не украшение: остановка
// сервиса обязана доходить до чужой работы, а не оставлять её сиротой. Без него
// конвертация шестичасовой записи переживает остановку воркера, а запрос к
// платному распознаванию висит до собственного таймаута библиотеки.
type AudioMetaViewer interface {
GetInfo(ctx context.Context, src string) (*AudioInfo, error)
}
type AudioFileConverter interface {
Convert(ctx context.Context, src, dest string) error
}
// AudioRecognizer — внешний распознаватель речи.
//
// Заливка и отправка операции разделены: это два обращения с разной ценой
// повтора. Повтор заливки бесплатен и кладёт объект под тем же ключом; повтор
// отправки оплачивается наружу, и шаг обязан проверить сделанное прежде, чем
// платить второй раз.
//
// Результат отдаётся **доменным** — реплики со временем, плоский текст и байты
// ответа на хранение, — а не сырым форматом провайдера: разбор потока это
// обязанность адаптера, и ни один шаг конвейера не знает, каким потоком и какими
// полями провайдер отвечает.
type AudioRecognizer interface {
// Provider — имя провайдера, под которым сохраняется попытка.
Provider() string
// Model — имя модели распознавания.
Model() string
// Upload кладёт аудио туда, откуда провайдер его прочитает, и отдаёт адрес.
// Повтор кладёт объект под тем же ключом и оплаты не стоит.
Upload(ctx context.Context, file io.Reader, objectKey string) (sourceURI string, err error)
// ObjectExists отвечает, лежит ли объект нужного размера. По нему шаг решает,
// повторять ли заливку; сверка содержимого хешем ненадёжна — признак
// целостности у составного объекта не равен отпечатку содержимого.
ObjectExists(ctx context.Context, objectKey string, size int64) (bool, error)
// Submit заводит операцию распознавания по адресу аудио. Оплачивается
// наружу.
Submit(ctx context.Context, sourceURI string) (operationID string, err error)
// CheckStatus опрашивает операцию.
CheckStatus(ctx context.Context, operationID string) (*entity.RecognitionResult, error)
// Fetch забирает готовый результат и отдаёт его доменным.
Fetch(ctx context.Context, operationID string) (*entity.RecognitionOutcome, error)
// Parse строит доменный результат из **сохранённого** ответа провайдера, не
// обращаясь к нему. По нему архив пересчитывается без единого рубля.
Parse(raw []byte) (*entity.RecognitionOutcome, error)
}