惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

AWS News Blog
AWS News Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - Franky
大猫的无限游戏
大猫的无限游戏
Engineering at Meta
Engineering at Meta
T
Tailwind CSS Blog
T
The Blog of Author Tim Ferriss
L
LangChain Blog
Vercel News
Vercel News
N
Netflix TechBlog - Medium
Hacker News - Newest:
Hacker News - Newest: "LLM"
Spread Privacy
Spread Privacy
小众软件
小众软件
H
Help Net Security
The Last Watchdog
The Last Watchdog
Forbes - Security
Forbes - Security
WordPress大学
WordPress大学
Know Your Adversary
Know Your Adversary
Recent Commits to openclaw:main
Recent Commits to openclaw:main
H
Heimdal Security Blog
GbyAI
GbyAI
P
Privacy International News Feed
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
The Cloudflare Blog
爱范儿
爱范儿
V
V2EX
The Register - Security
The Register - Security
B
Blog RSS Feed
Apple Machine Learning Research
Apple Machine Learning Research
O
OpenAI News
Cisco Talos Blog
Cisco Talos Blog
Cloudbric
Cloudbric
Security Archives - TechRepublic
Security Archives - TechRepublic
S
Secure Thoughts
L
LINUX DO - 最新话题
Recorded Future
Recorded Future
P
Proofpoint News Feed
PCI Perspectives
PCI Perspectives
Hugging Face - Blog
Hugging Face - Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tor Project blog
Latest news
Latest news
Project Zero
Project Zero
月光博客
月光博客
F
Fortinet All Blogs
A
Arctic Wolf
博客园 - 【当耐特】
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Y
Y Combinator Blog
N
News and Events Feed by Topic

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Веселимся со Spring: pet-проект по распознаванию речи
rurikovich · 2026-05-10 · via Все публикации подряд на Хабре

Привет Хабр !

Не писал на Spring уже лет 8 и решил по фану написать мини пет проект с api и распознаванием речи. Звучит круто, лет 8-10 назад это заняло бы … вечность, тогда и llm, достаточно качественно распознающих русскую речь, да еще на скромном домашнем пк не было. В общем решил в выходной повеселиться.


Итого я собрал маленькое Spring Boot приложение, которое принимает короткий WAV-файл, отправляет его в локальную модель распознавания речи и показывает текст на странице.

Проект лежит на GitHub: https://github.com/rurikovich/RememberSpring


1. Что делает приложение

Сценарий простой:

  1. Открываем страницу http://localhost:8080/.

  2. Выбираем WAV-файл с русской речью.

  3. Нажимаем кнопку перевести в текст.

  4. Сервер принимает файл.

  5. Vosk распознаёт речь.

  6. Страница показывает результат.

Ограничение специально небольшое: аудио до 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. Как запустить

Клонируем проект:

Команды запуска
git clone https://github.com/rurikovich/RememberSpring.git
cd RememberSpring
./mvnw spring-boot:run

Потом открываем:

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-канале.

Веселых Вам Выходных !

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

0%да0

0%это что то на программистском0

Никто еще не голосовал. Воздержавшихся нет.