Разработчики нередко сталкиваются с задачей написания инструкций. Каждый новый функционал требует подготовки документации, сопровождающейся созданием скриншотов, форматированием текста и тщательным редактированием.
Эти процессы отнимают много времени и сил.

О том, как мы разработали сервис, который упрощает процесс и значительно снижает время на подготовку инструкций читайте в этой статье.
Почему ручное написание инструкций — боль?
Идеально, если инструкции готовят специалисты, но реальность часто вынуждает самостоятельно заниматься этим процессом и рано или поздно приходится браться за написание инструкций к новому функционалу. В нашей компании, специализирующейся на внедрении 1С, такие задачи возникают регулярно.
Давайте детальнее рассмотрим процесс подготовки инструкций.

- Первый шаг — открыть текстовый редактор, куда мы непосредственно будем писать нашу инструкцию 
- Следующий этап – открыть программный продукт, который мы будем описывать 
- Далее – используем инструменты для работы с изображениями 
Скорее всего, этот процесс у большинства разработчиков организован аналогичным образом.
Необходимо постоянно перемещаться между программами, формулировать мысли и описывать процесс работы в разрабатываемом продукте.
«Вот было бы здорово, если бы инструкции создавались автоматически» - подумали мы
и разработали программу по автоматическому созданию инструкций.
Но нет. Полностью автоматизировать процесс пока не удалось, но мы значительно упростили его с помощью LLM и Whisper. 
Теперь мы пишем инструкции по следующему алгоритму:
- Записываем видео, на котором рассказываем, как работать в программном продукте 
- Отправляем видео в наш сервис подготовки инструкций 
- Получаем готовую текстовую инструкцию с разделами, абзацами и картинками 
- Результат — готовая структурированная инструкция в формате .docx. 
Архитектура нашего пайплайна выглядит следующим образом:
Получение ссылки в телеграм → Скачивание Видео → Формирование аудио → Делим файл на части → Транскрибация → Склейка результата → Разбивка на абзацы → Редактура → Определение вставки картинок → Разбивка на разделы → Формирование документа DOCX → отдаем файл в телеграм
 Детальный пайплайн этого процесса представлен ниже, предлагаем ознакомиться с ним.
Клиент взаимодействия
В качестве клиентской части используется телеграм-бот, это быстро и удобно. Пользователь отправляет телеграм-боту ссылку на видеоинструкцию, предварительно размещенную или на Яндекс-диске или Synology Drive (мы используем его как корпоративный сервис хранения файлов) и в ответ получает готовую инструкцию в формате word.

В качестве фрэймворк telegram используется распространённый python-telegram-bot
from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, ContextTypes
Бот получает от пользователя ссылку на скачивание и передает ее в класс Downloader.
Скачивание видеофайлов
Оказалось, не так просто скачать видео по ссылке и если с Яндекс-диском мы справились более-менее быстро, то с Synology пришлось повозиться. Ссылка расшаренного видеофайла не содержит прямой ссылки для скачивания, и чтобы получить ссылку пришлось использовать эмуляцию web-браузера, через который уже получаем ссылку на скачивание.
Класс SynologyDownloader реализует скачивание видео с веб-страницы Synology Drive:
- Запускает браузер через - Playwrightи переходит по URL, который отправил пользователь
- Перехватывает прямую ссылку на видео, отслеживая сетевые запросы с типом - media
- При наличии ссылки — скачивает файл через - requests, определяя расширение по MIME-типу
- Если URL не найден — ищет во фреймах страницы кнопку «Download», нажимает её и сохраняет файл, предложенный браузером 
- Сохраняет файл в папку сеанса 
Извлечение аудио и подготовка видео
После скачивания, идет этап подготовки видео и извлечения аудио. Пользователь может передать видеофайл любого формата, а нам необходим предварительный этап обработки.
Класс Prepare_files, подготавливает медиафайлы для дальнейшей транскрибации:
- Определяет тип файла — видео или аудио (через - ffprobe).
- 
Если это видео: - Конвертирует его в MP4 с кодеком H.264 
- Обрезает до - MAX_DURATION(пока ограничиваем длину файла в 30 минут).
- Извлекает монофоническое аудио (16 кГц), нормализует громкость и применяет фильтры (highpass/lowpass) для шумоподавления. 
- Сохраняет полученные файлы в папку сеанса и возвращает пути к обработанному видео и предобработанному аудиофайлу. 
 
Подготовка файла к транскрибации
Для транскрибации мы используем Whisper, он стабильно работает, обеспечивая хорошее качество, но он очень прожорлив на ресурсы и при транскрибации больших видео мы часто получали проблему с нехваткой памяти.

Для снижения нагрузки на память мы начали дробить большие файлы на мелкие сегменты, обрабатывая их по очереди освобождая ресурсы после каждого этапа. 
Но нельзя просто разрезать файл, есть риск того, что файл будет разрезан посредине слова и тогда будет потеря смысла.
Мы реализовали в методе split_audio_file следующий алгоритм деления файла на части:
- Определение необходимости разбиения: Если длительность аудио превышает заданный лимит ( - PART_DURATION_SECONDS, по умолчанию 5 минут), файл разбивается.
- Планирование точек разреза: Изначально точки разреза вычисляются через равные интервалы ( - PART_DURATION_SECONDS).
- 
Поиск подходящего места для разреза: - Для каждой запланированной точки вызывается - findsplit_point, который определяет окно поиска — последние- LOOKBACK_SECONDS(по умолчанию 60 сек) до целевой точки.
- В этом окне ищет паузу (тишину) с помощью - pydub.silence.detect_silence, используя поро�� громкости (- PAUSE_THRESHOLD_DB = -40 dB) и минимальную длительность паузы (- PAUSE_MIN_DURATION_MS = 2000 мс).
- Выбирает самую позднюю подходящую паузу до целевой точки, чтобы разрез прошёл «между словами». 
- Если пауза не найдена — разрез делается точно в целевой точке. 
 
Это позволило нам более аккуратно делить файл на части, конечно, есть риск, что мы не найдем нужной паузы, но мы все-таки работаем с инструкцией, где обычно дикторы говорят размеренно, делая паузы между фразами.
При разделении файла на части нас поджидал еще один «неудобный» момент, как whisper  транскрибирует файл зависит в том числе и от длины файла, точнее от контекста файла, который доступен whisper.
Например, если мы транскрибируем файл длинной полторы минуты и три файла по тридцать секунд результат будет немного другой, но в нашем случае это влияние не существенно.
Очистку памяти мы производим следующим образом:
- 
Очистка GPU-кэша через PyTorch. После транскрибации каждой части (и в конце полной транскрибации) вызывается: iftorch.cuda.is_available():torch.cuda.empty_cache()
- Удаление временного аудиофайла - os.unlink(part_file)
- Дополнительно при завершении обработки всего файла. Модель остаётся загруженной в памяти до вызова метода - unload(), где явно удаляются- self.model, self.pipe, self.processorи снова вызывается- torch.cuda.empty_cache().
Транскрибация
Файл транскрибируется по частям для экономии памяти, обычно мы используем модель whisper large-v3, но можно передать другую модель: "tiny", "base", "small", "medium", "large", "large-v2", "large-v3" и т.д.
Русский язык модель транскрибирует в общем неплохо, но много зависит от качества исходной речи, скорости речи диктора и специфики текста.
Язык задан жёстко как "ru" (русский) в вызове transcribe().
result = self.model.transcribe(
 part_file,
 language="ru",
 word_timestamps=True,
 prompt=self.prompt
 )
Whisper поддерживает передачу промта для уточнения контекста, но в наших экспериментах это не дало заметного улучшения качества транскрибации.
После транскрибации частей файла, система склеивает результат и на выходе мы получаем json формата.

Разделение текста на абзацы
После получения текста его необходимо дополнительно обработать.
Первым шагом мы разбиваем текст на абзацы по следующему принципу:
- Разбивает входной текст на предложения (с помощью NLTK, с поддержкой русского языка) 
- Получает эмбеддинги предложений с помощью предобученной модели all-MiniLM-L6-v2 
- Вычисляет попарное косинусное сходство между соседними предложениями. 
- 
Формирует абзацы, последовательно добавляя предложения, пока не выполнится одно из условий: - Семантическое сходство между текущим и следующим предложением ниже заданного порога ( - threshold) и текущий блок уже достаточно длинный (- min_sentsили- min_words).
- Блок превышает допустимые размеры ( - max_sentsили- max_words)
 
На выходе мы получаем список кортежей (абзац и временная метка последнего предложения в абзаце).
Пример:
[
 ("Первый абзац...", 12.4),
 ("Второй абзац...", 28.7)
 ]
Улучшаем текст
Анализ видеозаписей позволил нам сделать важное наблюдение: письменная речь заметно отличается от устной. Когда человек произносит текст вслух, его речь часто бывает менее структурированной и лаконичной по сравнению с письменной формой. Это выражается в использовании слов-паразитов, повторениях и нечетких формулировках, тогда как написанный текст  выглядит более гладким и логичным.
Учитывая это различие, мы приняли решение предварять обработку текста специальным этапом его коррекции. Для этого была разработана специальная инструкция («промпт»), которая помогает преобразовать разговорную речь в более читаемый и понятный формат.
Эта инструкция задаёт правила для языковой модели и позволяет улучшить структуру и стиль получаемого текста.
prompt = "
Ты — профессиональный редактор и технический писатель. Твоя задача — улучшить следующий текст, полученный из аудиозаписи видеоинструкции. Сделай его грамматически правильным, стилистически гладким, логически связным и литературно выдержанным, полностью сохранив все исходные сведения.
Правила обработки:
1. Удали слова-паразиты (например: "ну", "вот", "как бы", "это самое", "значит", "короче", "типа" и т.п.), но не исключай ни одной смысловой детали.
2. Исправь разговорные, нечёткие или грамматически неверные формулировки, переформулируй их ясно и профессионально, не искажая смысла.
3. Сохрани все технические термины, шаги, примеры, уточнения и повторы — даже если они кажутся избыточными.
4. Не добавляй новые идеи, пояснения, комментарии или выводы.
5. Не сокращай и не обобщай — каждая мысль должна остаться, но быть выражена правильно и красиво.
6. Верни только улучшенный текст, без пояснений, заголовков или комментариев.
7. Не возвращай в тексте свои рассуждения, только итоговый текст
"
Мы старались, чтобы LLM не искажала текст, а в меру его улучшало. Не всегда это работает хорошо, но в общем, результат нас удовлетворил.
Как мы научили ИИ понимать, где нужны иллюстрации?
И конечно, любая хорошая инструкция не обходится без визуализации. Изображения играют ключевую роль в понимании нового функционала, поскольку помогают пользователям быстрее разобраться в особенностях интерфейса и порядке действий. Поэтому особое внимание уделяется созданию качественных иллюстраций и скриншотов, которые делают инструкцию нагляднее и доступнее.
Мы пробовали разные способы добавления картинок:
- Равномерно через равные промежутки времени 
- 
По кодовому слову, например, если встречается текст «Сейчас на экране» Но потом решили отдать инициативу добавления иллюстраций LLM. Вот что у нас получилось: 
- В цикле происходит анализ каждого абзаца на предмет необходимости вставки изображения. Мы использовали следующий промт: 
prompt = 
"Ты — эксперт по технической документации. Определи, требует ли данный фрагмент инструкции вставки иллюстрации (скриншота, схемы, фото интерфейса и т.п.).
Придерживайся следующих правил, иллюстрация нужна, если в тексте:
- описывается конкретный элемент инте��фейса (кнопка, меню, поле ввода и т.д.)
- даётся пошаговое руководство с действиями пользователя (нажать, выбрать, перейти, открыть, следует перейти, необходимо перейти  и т.п.)
- упоминается визуальный результат (вы увидите, на экране появится, как показано на рисунке, в данном элементе)
- есть ссылка на расположение чего-либо (в левом верхнем углу, под заголовком, она заполняется  и т.п.)
- при необходимости догадывайся по контексту.
Если иллюстрация полезна или необходима для понимания — ответь «1».
Если не нужна — ответь «0».
Ответ должен содержать только одну цифру: 1 или 0. Без пояснений, без знаков препинания."
- Анализирует ответ от LLM и при необходимости добавления иллюстрации сопоставляет номер абзаца с временной меткой в исходном видео 
paragraphs_time_scr = {
 3: 42.5,
 7: 118.0,
 12: 205.3
 }
Таким образом, словарь хранит связь между номерами абзацев и временными метками, соответствующими моменту вставки изображений.
Данные paragraphs_time_scr означают, что после завершения третьего абзаца необходимо добавить изображение, соответствующее фрагменту видео, начиная с отметки 42,5 секунды и так далее.
Эта информация используется нами на этапе формирования финального файла.
Разбиваем текст на разделы
Чтобы инструкция была структурированная необходимо разбить текст на разделы. Мы применяем следующий алгоритм:
- Входные данные: список абзацев, полученных из расшифровки аудио. 
- Разбиение на чанки: текст делится на части по 20 абзацев (чтобы уложиться в контекст LLM). 
- Анализ через LLM: каждый чанк отправляется в LLM с просьбой определить, с каких абзацев начинаются новые логические разделы, мы используем такой промт: 
prompt = "
 Ты — эксперт по технической документации. Проанализируй пронумерованные абзацы инструкции и определи, с каких абзацев начинаются новые логические разделы.
Разделы в инструкциях обычно включают:
 - Введение / Обзор
 - Требования / Предварительные условия
 - Пошаговые действия (например: «Шаг 1», «Настройка», «Запуск»)
 - Проверка результата / Верификация
 - Устранение неполадок
 - Заключение / Дополнительные рекомендации
ВАЖНО:
 - Первый абзац ВСЕГДА является началом первого раздела.
 - Раздел начинается, если:
 • Тема или задача меняется;
 • Начинается новый этап инструкции;
 • Появляется подзаголовок по смыслу (даже если он не выделен явно).
 - Не выдумывай разделы — опирайся только на содержание текста.
 - Не включай промежуточные пояснения, примеры или примечания как отдельные разделы, если они не начинают новую тему.
Формат ответа — строго по одной строке на раздел:
 <номер_абзаца> <название_раздела>
Требования к формату:
 - Номер — целое число без скобок.
 - Название — краткое (3–6 слов), отражающее суть раздела, в стиле технической инструкции.
 - Никаких дополнительных символов, пояснений или пустых строк.
Пример корректного ответа:
 1 Введение
 4 Предварительные требования
 7 Шаг 1: Запуск приложения
 12 Шаг 2: Настройка параметров
 18 Проверка результата
 22 Заключение
 "
Результат: список разделов вида [{'title': 'Назва��ие', 'start_par': N, 'end_par': M}, ...]
 который затем используется для структурированного оформления документа .docx.
Формирование файла DOCX
После всех подготовительных действий формируется файл docx.
Создаётся объект Document() из python-docx и далее для каждого раздела:
- Добавляется заголовок (level=1). 
- Поочерёдно добавляются абзацы (с табуляцией \t). 
- 
После абзаца, номер которого есть в paragraphs_time_scr, извлекается кадр из видео в указанное время и вставляется в документ, предварительно картинка сжимается, чтобы word файл не был очень большим.import uuid
 h, w = frame.shape[:2]
 max_width = 1280 # максимальная ширина в пикселях
 if w > max_width:
 scale = max_width / w
 new_w = max_width
 new_h = int(h * scale)
 frame = cv2.resize(frame, (new_w, new_h), interpolation=cv2.INTER_AREA)cv2.imwrite(output_image_path, frame, [cv2.IMWRITE_JPEG_QUALITY, 90])Файл сохраняется с тем же именем, что и файл видео, но расширением .docx и отправляется в телеграм пользователю. 
 В итоге мы получаем такой файл: Пример полученной инструкции 
Итоги: сколько времени экономим?
Мы создавали этот сервис в рамках Клуба разработчиков. В Клубе разработчиков мы с коллегами реализуем небольшие, но полезные пет-проекты. Прежде всего мы учимся, повышаем свой профессиональный уровень и стараемся создавать полезные сервисы. Один из таких проектов – сервис, помогающий автоматизировать процесс написания инструкций.
Я лично подготовил несколько инструкций используя этот сервис, безусловно, после получения файла необходимо внести небольшие правки в текст вручную. Не всегда удаётся сразу корректно сформулировать речь при записи видео, поэтому после транскрипции текст требует минимальных доработок. Иногда сервис может ошибочно определить границы разделов, однако даже с учётом этих правок общее время подготовки инструкции сокращается в разы.
В качестве LLM мы используем gemma3:12 Ollama.
Спасибо, кто дочитал до конца, если у Вас есть предложения или мысли по улучшению сервиса с удовольствием обсудим в комментариях.
 
           
 
BadNickname
То есть инструкции стали ещё более бесполезными...
peston Автор
зависит от того, что именно рассказывать, но можно сэкономить время на подготовку