|
Professor Seleznov
|
Привет Хабр ! Не писал на Spring уже лет 8 и решил по фану написать мини пет проект с api и распознаванием речи. Звучит круто, лет 8-10 назад это заняло бы … вечность, тогда и llm, достаточно качественно распознающих русскую речь, да еще на скромном домашнем пк не было. В общем решил в выходной повеселиться.
-Итого я собрал маленькое Spring Boot приложение, которое принимает короткий WAV-файл, отправляет его в локальную модель распознавания речи и показывает текст на странице. Проект лежит на GitHub: https://github.com/rurikovich/RememberSpring - 1. Что делает приложение Сценарий простой:
- Открываем страницу http://localhost:8080/.
- Выбираем WAV-файл с русской речью.
- Нажимаем кнопку перевести в текст.
- Сервер принимает файл.
- Vosk распознаёт речь.
- Страница показывает результат.
 Ограничение специально небольшое: аудио до 10 секунд. Для учебного проекта этого хватает. - 2. На чём написано Стек получился такой:
- Java 21
- Spring Boot 4
- Spring Web
- Vosk Java API
- простая HTML-страница без React и прочего фронтенд-зоопарка
- модель vosk-model-small-ru-0.22
Vosk подключается как Maven-зависимость:
pom.xml: зависимости
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alphacephei</groupId> <artifactId>vosk</artifactId> <version>0.3.45</version> </dependency>
Тут есть маленькая деталь: именно com.alphacephei:vosk, а не org.vosk:vosk. - 3. Немного про модель Для распознавания используется vosk-model-small-ru-0.22. Это небольшая русская модель для Vosk. Она работает локально и не требует облачного API. То есть не нужны токены, аккаунты, лимиты запросов и оплата за каждую попытку. Плюсы:
- работает офлайн;
- есть Java API;
- легко положить рядом с учебным проектом;
- для коротких фраз качество нормальное.
Минусы тоже есть:
- это не большая современная модель уровня Whisper;
- качество зависит от шума, микрофона и произношения;
- модель занимает место в репозитории, если её коммитить.
В проекте модель лежит так:
Структура папки models
models/ vosk-model-small-ru-0.22/ am/ conf/ graph/ ivector/ README
- 4. Настройки приложения В application.yaml задаём имя приложения, лимит загрузки файла и путь до модели.
application.yaml
spring: application: name: RememberSpring servlet: multipart: max-file-size: 25MB max-request-size: 25MB asr: max-duration-seconds: 10 vosk: model-path: ${VOSK_MODEL_PATH:models/vosk-model-small-ru-0.22}
- 5. REST-контроллер Контроллер здесь простой. Он принимает файл, вызывает сервис распознавания и возвращает JSON.
TranscriptionController.java
@RestController @RequestMapping("/api") public class TranscriptionController { private final VoskTranscriptionService transcriptionService; public TranscriptionController(VoskTranscriptionService transcriptionService) { this.transcriptionService = transcriptionService; } @PostMapping( value = "/transcribe", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE ) public ResponseEntity<TranscriptionResponse> transcribe(@RequestPart("file") MultipartFile file) { try { TranscriptionResult result = transcriptionService.transcribe(file); return ResponseEntity.ok(new TranscriptionResponse( result.text(), result.durationSeconds(), "Vosk", "ru" )); } catch (IllegalArgumentException e) { throw new ResponseStatusException(BAD_REQUEST, e.getMessage(), e); } catch (IllegalStateException e) { throw new ResponseStatusException(SERVICE_UNAVAILABLE, e.getMessage(), e); } } }
Отдельно обрабатываются две ситуации:
- пользователь прислал неправильный файл;
- модель не найдена или не загрузилась.
Для учебного проекта этого достаточно. В боевом сервисе я бы добавил нормальный формат ошибок, request id и метрики. - 6. Сервис распознавания Основная работа находится в VoskTranscriptionService. Что он делает:
- проверяет, что файл не пустой;
- лениво загружает модель Vosk;
- читает WAV через Java Sound API;
- приводит аудио к PCM 16 kHz mono;
- проверяет длительность;
- прогоняет байты через Recognizer;
- достаёт поле text из ответа модели.
VoskTranscriptionService.java: главная часть
public synchronized TranscriptionResult transcribe(MultipartFile file) { if (file == null || file.isEmpty()) { throw new IllegalArgumentException("Файл пустой. Передайте короткий .wav файл."); } Model loadedModel = getOrLoadModel(); try ( AudioInputStream sourceStream = AudioSystem.getAudioInputStream( new BufferedInputStream(file.getInputStream())); AudioInputStream pcmStream = AudioSystem.getAudioInputStream(TARGET_AUDIO_FORMAT, sourceStream); Recognizer recognizer = new Recognizer(loadedModel, TARGET_SAMPLE_RATE) ) { double durationSeconds = calculateDurationSeconds(sourceStream); if (durationSeconds > maxDurationSeconds) { throw new IllegalArgumentException( "Файл длиннее " + maxDurationSeconds + " секунд. Укоротите запись и попробуйте снова."); } byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; while ((bytesRead = pcmStream.read(buffer)) != -1) { if (bytesRead > 0) { recognizer.acceptWaveForm(buffer, bytesRead); } } String finalResultJson = recognizer.getFinalResult(); return new TranscriptionResult(extractText(finalResultJson), durationSeconds); } catch (UnsupportedAudioFileException e) { throw new IllegalArgumentException("Неподдерживаемый формат аудио. Для демо используйте WAV.", e); } catch (IOException e) { throw new IllegalStateException("Не удалось прочитать аудиофайл.", e); } }
Почему метод synchronized? Чтобы в маленьком учебном приложении не ловить одновременную работу нескольких Recognizer на одном сервисе и не усложнять код пулом задач. Для одного пользователя и демо-страницы нормально. В продакшене я бы так не оставил. Там уже нужны очередь, worker-пул и ограничения по нагрузке. - 7. HTML-страница Фронтенд тут специально минимальный. Один index.html в src/main/resources/static. Spring Boot сам отдаёт его по адресу http://localhost:8080/.
index.html: отправка файла
<form id="transcriptionForm"> <label for="audioFile">Аудиофайл</label> <input id="audioFile" name="file" type="file" accept="audio/wav,.wav" required> <button id="submitButton" type="submit">перевести в текст</button> </form> <div id="result" class="result" hidden></div>
const formData = new FormData(); formData.append('file', fileInput.files[0]); const response = await fetch('/api/transcribe', { method: 'POST', body: formData }); const payload = await response.json(); showResult(payload.text || 'Модель не распознала текст.', false);
Без сборки фронтенда. Без npm. Без отдельного dev-server. Для учебной статьи это плюс. Меньше магии вокруг, проще увидеть саму механику: файл ушёл на сервер, сервер вернул текст. - 8. Как запустить Клонируем проект:
Потом открываем:
http://localhost:8080/
Выбираем WAV-файл до 10 секунд и нажимаем кнопку. Если хочется проверить без страницы:
curl-запрос
curl -X POST "http://localhost:8080/api/transcribe" \ -F "file=@sample.wav"
Ответ будет примерно такой:
Пример JSON-ответа
{ "text": "тестовое сообщение для тестового проекта длиной пять секунд", "durationSeconds": 5.0, "engine": "Vosk", "language": "ru" }
- 9. Что можно улучшить Для учебного проекта всё ок. Но если делать нормальный сервис, я бы первым делом поменял несколько вещей. Во-первых, не выполнять распознавание прямо в HTTP-запросе. Лучше принимать файл, отдавать jobId, а распознавание делать в фоне. Во-вторых, добавить поддержку форматов через отдельный декодер. Сейчас демо работает только с WAV. А пользователь легко принесёт OGG, MP3 или что-то из мессенджера. В-третьих, модель не всегда стоит коммитить в репозиторий. Для статьи это удобно: скачал проект, запустил, всё рядом. Для рабочего проекта модель лучше хранить как отдельный артефакт. Ну и метрики. Без них не понятно: модель долго думает, файл плохой, запись шумная или сервер просто не тянет. - 10. Итог Получился маленький, но интересный пример:
- Spring принимает файл;
- Vosk распознаёт русскую речь локально;
- HTML-страница показывает результат;
- код можно запустить без облачных ключей.
Мне такие учебные проекты нравятся больше абстрактных примеров. Они не очень большие, но в них уже есть настоящие проблемы: лимит загрузки файла, путь до модели, формат аудио, обработка ошибок.-Если вам близки темы разработки, рефакторинга, архитектуры и стартапов буду рад видеть вас в моём Telegram-канале. Веселых Вам Выходных !-Источник
|