Зачем писать дневник, если можно его надиктовывать? Казалось бы, здравая мысль, но как потом с ним работать? И нужен ли он вообще в таком виде? Сейчас есть довольно большое количество программ, которые сразу сделают speech‑to‑text, и проблемы не будет. А что, если такая идея возникла «‑дцать» лет назад, когда деревья были большими, а в телефонах только‑только появилась функция «диктофон»? За многие годы в нашей домашней аудиотеке накопилось более 30,000 таких записей. Пришла пора разложить всё по полкам, конечно же, с «преферансом и куртизанками».
Несколько лет назад, параллельно с фотографиями, я также начал раскладывать все аудиозаписи по папкам «Год/Месяц». Однако, если для фотографий я реализовал распознавание лиц, предпросмотр альбома и другие функции, то с аудиозаписями всё по‑прежнему оставалось печально. Да, добавление даты записи в имя файла уже улучшило наглядность и структуру, но хотелось большего.
Я начал с изучения решений, которые уже были на рынке. К сожалению, все они ориентировались на то, что записи будут изначально создаваться в них, либо предлагали добавлять каждую запись вручную. Понятно, что в случае 30,000 файлов это задача далека от тривиальной.
Тогда было решено создать свой инструмент, тем более что за плечами был успех с фотографиями. План максимум включал: распознавание голоса говорящего, распознавание текста и составление заголовка или краткого содержания записи. Конечно, с использованием нейросетей — куда же сейчас без них? Облачные решения, как и в случае с фотографиями, пришлось отклонить из‑за стоимости. К тому же, на моем домашнем сервере есть видеокарта с поддержкой CUDA, так что локальный вариант был более интересен.
Изначальный идеальный вариант выглядел так:
Но оказалось, что не всё так просто. Да, есть весьма достойные алгоритмы диаризации (например тут или тут), но все они более‑менее неплохо работают, когда заранее известно количество спикеров. В моем случае это не так. Да и, как показал предварительный анализ, в 95% случаев спикер только один, и это почти всегда жена, так что на идентификацию тоже было решено забить.
Распознавание текста — задача уже более типичная, и готовых библиотек уйма. Я остановился на whisper. Она довольно проста в использовании, поддерживает ускорение с помощью CUDA и при этом дает весьма приличный результат. Ну, по крайней мере, так изначально показалось.
На хороших записях распознавание почти идеальное, а учитывая то, что она ещё расставляет пунктуацию, то просто отлично. Проблемы начались, когда стали попадаться записи, которые содержали не голоса, а, например, музыку, пение птиц или чьё‑то пыхтение (гусары, молчать!). В таких случаях нейросеть всё равно не сдаётся и начинает «галлюцинировать», т. е. придумывать то, чего нет, выдавая это за истину. Этот неприятный эффект я обнаружил уже после того, как было обработано несколько тысяч записей. Причем первое обнаружение было очень забавным: в одном из файлов в качестве текста было «С вами был Игорь Негода!». Я глубоко уважаю данного блогера‑самоделкина и даже заподозрил, что фраза могла реально попасть случайно на запись. Но поиск показал, что таких записей несколько десятков, а в самих файлах тишина. Дальнейший анализ выявил, что данную нейросеть, судя по всему, обучали на субтитрах с YouTube, так как 90% галлюциногенных фраз состояло из слов, которые видеоблогеры говорят в конце роликов: «Ставьте лайки», «Подписывайтесь на канал» и так далее. Поэтому я составил пару десятков регулярных выражений, которые покрыли практически все галлюциногенные фразы. По крайней мере, пара сотен прослушанных и выверенных записей больше не показали ничего странного.
Следующим этапом было составление заголовков, но и тут меня постигло фиаско: с десяток проверенных нейросетей выдавали просто треш. Судя по всему, добрую их часть обучали на новостях, так как большинство заголовков звучали как из бульварной прессы, а на пустые тексты часть нейронок выдавала вообще фразы про главу государства. Я уж даже было подумал обратиться к всемогущему ChatGPT, да это уже не локальное решение, но и он сплоховал. Нет, он работал хорошо, вот только заголовки, по большей части, были вроде «Семейная беседа». Да, это так, но, к сожалению, не очень выполняет основную функцию быстрой идентификации записей. И тут, как ни странно, оказалось, что то, что мне было нужно, было совсем рядом. В первом предложении чаще всего содержалась, если не тема записи, то, по крайней мере, явные ориентиры, о чем она будет. Так что, как говорится, всё гениальное просто: в качестве заголовка я взял первое предложение, и это оказалось достаточно информативным.
После распознавания записей встал вопрос: как с этим всем работать? Просто 30,000 txt‑файлов — явно не то, с чем хочется работать в 21 веке. Первая мысль была: закинуть всё в базу данных и написать простенький фронтенд. Но поскольку я не очень умею «во фронтенд», и, к тому же, хотелось сохранить это где‑то вне своих серверов для репликации данных, я решил рассмотреть альтернативы. Первый вариант был Google Docs, но встраивание ссылки на аудио там, без танцев с бубном, не работает, да и вариант иметь один документ на все записи, так же как и вариант иметь 30,000 документов, казался не очень удобным.
Тогда я обратил внимание на Notion: удобный API, возможность создать структуру документов, встроить аудио и, главное, возможность искать по записям «из коробки». Набросал прототип страницы, попробовал попользоваться, оказалось очень удобно.
Начал писать программу для выкладки. Наиболее удобной библиотекой показалась notion‑py. Всё было хорошо, пока я не выложил первые пять тысяч записей. Оказалось, что система кеширования в этой библиотеке организована таким образом, что обновление индекса происходит одним запросом, и, в какой‑то момент, это превышает максимальный размер данных, принимаемых сервером. Плюс к этому, когда записей становится много, библиотека начинает жутко тормозить. Сначала я попробовал пофиксить баги в библиотеке самостоятельно, но оказалось, что их там немало, а библиотека, по сути, заброшена и не поддерживается.
Так что, после пары вечеров копания в легаси, я решил рассмотреть альтернативы. А их, к сожалению, немного, только официальный API. В нём всё хорошо, но… Он не полный. Первое и самое главное — он не поддерживает загрузку файлов на сайт, только возможность вставить ссылку. Второе — он не позволяет заблокировать страницу, и по умолчанию она остаётся в режиме редактирования, что также не очень удобно, так как это у меня архив и редактировать его на сайте — плохая функция, вводящая в заблуждение. А ещё через него нельзя удалять записи (что, например, необходимо для обновления страниц). Поэтому, в конечном варианте, пришлось использовать оба подхода. Основная часть взаимодействия идёт через официальный API, а недостающая — через notion‑py (с одним критичным для моей задачи багфиксом).
В итоге получилась система из двух скриптов:
-
Первый скрипт распознает аудио и создает в той же папке json-файл с таким же именем, который содержит текст, описание и прочие необходимые атрибуты. Это длительная фоновая работа, которую я запускал на ночь по месяцам.
~> mmdiary-transcriber-run имя_папки
Пример файла:
{
"caption": "Audio Note Caption 1",
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit....",
"model": "whisper/medium",
"type": "audio",
"source": "2020-02-07_22-11-16.mp3",
"duration": 5.3,
"recordtime": "2020-02-07 22:11:16",
"processtime": "2024-03-15 01:35:13"
}
-
Второй скрипт синхронизирует локальные файлы с Notion. Относительно быстрая операция, которую можно выполнить и сразу увидеть результат на сайте.
~> mmdiary-notion-upload имя_папки
Но, после отладки, (и добавления функции работы с видео, но об этом в следующей статье) был написан общий скрипт, который выполняет сразу все этапы и, к тому же, может запускаться автоматически по cron‑у.
~> mmdiary --audio --notion
Теперь, как и с фотографиями, достаточно вставить флешку (или поместить файлы в особую папку на сервере), нажать кнопку для импорта, и, спустя некоторое время, все записи, сопровождаемые расшифровкой в удобном текстовом виде с возможностью поиска и прослушивания аудиозаписей, появятся в Notion:
В дополнение к системе просмотра записей мне захотелось иметь что‑то, что будет периодически напоминать о них. В этом плане мне нравится, как устроен Google Photos, который раз в день присылает различные воспоминания. А поскольку в нашей семье основной платформой для взаимодействия и общения является Telegram, то было решено просто написать бот. Функционал простой: раз в день присылать случайную аудиозапись, а также предоставлять записи за определенный день по запросу. Описывать весь процесс создания телеграм‑бота будет излишним, так как это уже отдельная тема, и статей об этом великое множество, так что просто оставлю ссылку на код бота.
Ну и, собственно, код всего проекта также доступен:
https://github.com/sashacmc/mmdiary
Как всегда, все open source, буду рад если мой опыт ну или код будет полезен еще кому-то.
Автор: sashacmc