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) }