2026-09-16 08:32:06 +03:00
2026-09-16 08:32:06 +03:00
2026-09-16 08:32:06 +03:00
2026-09-16 08:32:06 +03:00
2026-09-16 08:32:06 +03:00
2026-09-16 08:32:06 +03:00
2026-09-16 08:32:06 +03:00
2026-09-16 08:32:06 +03:00

2026-linux-sumka

Сервис для построения полноценного файла коспекта (главным образом Markdown) из записи в одном из видов:

  1. Только звук. Создаст файл лекции, основываясь только на звуке.
  2. Видео со звуком. Создаст файл лекции на основе звука, и дополнит информацию, используя кадры из видео.

Для запросов к ИИ используется OpenAI-compatible API, поэтому использовать можно разные модели. Разработка велась в связке с GigaChat и с локальной ollama через посредство OpenWebUI. В репозитории хранятся промтпы для абстрактной языковой модели, и, вероятно, их лучше поменять в проде. К тому же, промпты написаны на русском языке, а иностранные модели лучше работают со служебными промптами на английском языке.

Установка (Docker)

TODO

Установка (Python)

Для разработки и тестирования используется Python 3.13.1.

# Клонировать репозиторий и перейти в него
git clone https://git.tyukalov.su/nikita/2026-linux-sumka
cd 2026-linux-sumka
# Создать venv (либо ваш способ, если шарите)
python3 -m venv .venv
# Активировать виртуальное окружение
. .venv/bin/activate
# Установить зависимости
pip install -r requirements.txt

# Запуск вот так (см. далее)
python main.py

Запуск

TODO

Архитектура

Предполагается, что приложение будет запускаться сторонним приложением всякий раз, когда требуется произвести конвертацию медиафайла в конспект (например, Telegram ботом, которому отправили видео).

Сервис не сохраняет никаких данных между перезапусками. Всё, что сохраняется - это промежуточные результаты. Например, если сервис сгенерировал файл asr_events.json, а после этого его принудительно завершили, то при следующем запуске он будет использовать этот файл, чтобы не повторять дорогие операции. Поэтому при автоматизации рекомендуется на каждый новый запрос создавать новую промежуточную директорию, а старые директории удалять, когда они становятся не нужны.

Алгоритм работы сервиса следующий:

  1. Проверить, является входной файл звуком или видео со звуком
    • Звуковой файл - сразу начать работу
    • Видеофайл со звуком - разделить файл на звук и видео, используя ffmpeg, и перейти к обработке звука
    • Видеофайл без звука - не поддерживается
    • Ни одна из категорий - ошибка
  2. Выполнить распознание речи
    • Используется Whisper
    • Базовые настройки распознания должны быть доступны для изменения через CLI, релевантные аргументы должны начинаться с asr-
    • В результате распознания речи в промежуточной директории должен появиться файл asr_raw.json. В корне обязательно должен быть ключ, где указано название движка распознания (поддерживается только whisper). Каждый сегмент обязан содержать ключи start (время начала сегмента), end (время конца сегмента), text (текст сегмента). Затем должен идти ключ custom, где указываются значения, специфичные для движка (они будут использованы только на следующем шаге, а затем будут удалены).
      {
          "engine": "whisper",
          "segments": [
              {
                  "start": 0.0,
                  "end": 3.0,
                  "text": "текст речи с 0.0 по 3.0",
                  "engine": {
                      "temperature": 0.4,
                      "something": -5.6
                  }
              },
              {
                  "start": 3.0,
                  "end": 7.0,
                  "text": "текст речи с 3.0 по 7.0",
                  "engine": {
                      "temperature": 0.1,
                      "something": 542
                  }
              }
          ]
      }
      
  3. Выполнить фильтрацию полученных сегментов
    • Смысл этого шага - отсеять сегменты которые можно по имеющимся данным отнести к лишним.
    • Этот шаг формально существует, но на самом деле в текущей реализации на нем просто выбрасываются лишние данные из asr_raw.json и сегментам присваиваются идентификаторы.
    • В итоге должен получится файл asr.json в таком формате:
      {
          "segments": [
              {
                  "id": 0,
                  "start": 0.0,
                  "end": 3.0,
                  "text": "текст речи с 0.0 по 3.0"
              },
              {
                  "id": 1,
                  "start": 3.0,
                  "end": 7.0,
                  "text": "текст речи с 3.0 по 7.0"
              }
          ]
      }
      
  4. Построить список событий
    • Дальнейшие шаги требуют представления звука в виде списка событий
    • Для построения списка событий используется ИИ
    • Расшифровка скармливается ИИ постепенно, запрос состоит из двух сообщений, которые модели предлагается продолжить. Первое сообщение - системный промпт, где для модели разъясняется, что от неё требуется, а также описывается формат данных. Второе сообщение - сам запрос к модели. Он имеет следующий формат:
      {
          "past": [
              {
                  "id": 40,
                  "start": 58.0,
                  "end": 60.0,
                  "text": "фаышв"
              }
          ],
          "present": [
              {
                  "id": 41,
                  "start": 60.0,
                  "end": 64.0,
                  "text": "ащфзо"
              },
              {
                  "id": 42,
                  "start": 64.0,
                  "end": 70.0,
                  "text": "зпщыфвол"
              },
          ],
          "future": [
              {
                  "id": 43,
                  "start": 70.0,
                  "end": 73.0,
                  "text": "полытаы"
              }
          ],
          "context": {}
      }
      
      • present - это события, которые попадают в скользящее окно нефиксированного размера. Размер окна в текущем запросе определяется переменными asr_mmax_size и asr_mmin_segments. Окно должно содержать как можно больше сегментов, но сумма длин всех text, входящих в окно, не должна превышать asr_mmax_size; в то же время количество сегментов в окне не должно быть меньше, чем asr_mmin_segments, и это требование - самое приоритетное.
      • past и future - это события, которые идут до и после текущего окна. Их размеры определяются аналогично present, но используются переменные asr_cmax_size и asr_cmin_segments.
      • context - это контекст, формат которого модель определяет сама. Его значение сохраняется между запросами к модели. В системном промпте рекомендуется строго задать формат данных.
    • Задача модели после получения промтпа - объединить сегменты распознания речи в так называемые события, которые будут использованы в дальнейшнем. Модели на этом этапе разрешается удалять сегменты, логически объединять их содержимое, но строго запрещается изменять смысл, формулировки. Также модели разрешается исправлять пунктуацию или очевидные ошибки распознания.
    • Модель должна отвечать в следующем виде:
      {
          "preevents": [
              {
                  "ids": [0, 1],
                  "text": "объединенный текст для ID 0 и 1"
              },
              {
                  "ids": [2, 3, 4, 5],
                  "text": "объединенный текст для ID 2, 3, 4 и 5"
              },
              {
                  "ids": [8],
                  "text": "заметьте, модель выбросила сегменты 6 и 7, а этот сегмент сохранила без объединения с другими"
              }
          ],
          "context": {
              "for_future_call": "Сегменты 6, 7 выброшены, потому что..."
          }
      }
      
      • preevents - список недособытий. Время намеренно не должно быть использовано, так как оно должно вычисляться программно на основе значения ids
      • context - данные, которые модель хочет передать следующей итерации; формат определяется моделью или промптом
    • Важно, что модели следует явно запретить объединять сегменты, если объединение затрагивает future. В промпте следует явно указать, что в такой ситуации (когда нужно объединить что-то между present и future), модель должна сделать для себя пометку в контексте, и при следующей итерации объединить сегменты из past и present. Если не ограничить модель в этом, то начнется сущий кошмар.
    • Нужно после каждой итерации проверять, что указанные нейросетью ID действительно существуют, и что они входят в past или present. В следующей итерации модели больше нельзя передавать данные, которые были объединены, либо которые хоть раз попадали в past.
    • По мере получения объектов preevent нужно составлять файл audio_events.json:
      {
          "events": [
              {
                  "id": 0,
                  "type": "voice",
                  "timestamp": 0.0,
                  "duration": 7.0,
                  "text": "Здесь уже должны быть осмысленные законченные фразы",
                  "payload": null
              },
              {
                  "id": 1,
                  "type": "voice",
                  "timestamp": 7.0,
                  "duration": 3.0,
                  "text": "Обратите внимание, нет связи между ID здесь и раньше",
                  "payload": null
              }
          ]
      }
      
      • id присваивается независимо от того, что наблюдалось ранее. Здесь id - это просто число, увеличивающееся с ростом времени.
      • type на этом шаге может быть только voice. В этот общий список могут добавляться события других типов на более поздних шагах.
      • timestamp - начало события
      • duration - длительность события
      • text - текст
      • payload - для voice может быть только null
  5. Построить список ссылок на видео
    • Если видео нет, то шаг пропускается. Вместо него файл audio_events.json копируется в events.json и происходит переход на следующий шаг.
Description
Оркестратор ИИ для создания конспектов лекций одним запросов к сервису
Readme 288 KiB
Languages
Python 97.8%
Shell 1.1%
Dockerfile 1.1%