SayFable — офлайн-библиотека с NLP-разметкой, синтезом на 68 языков и чатом по книге. ios.. ios. Natural Language Processing.. ios. Natural Language Processing. nlp.. ios. Natural Language Processing. nlp. rag.. ios. Natural Language Processing. nlp. rag. silero.. ios. Natural Language Processing. nlp. rag. silero. Swift.. ios. Natural Language Processing. nlp. rag. silero. Swift. искусственный интеллект.. ios. Natural Language Processing. nlp. rag. silero. Swift. искусственный интеллект. малоресурсные языки.. ios. Natural Language Processing. nlp. rag. silero. Swift. искусственный интеллект. малоресурсные языки. Машинное обучение.. ios. Natural Language Processing. nlp. rag. silero. Swift. искусственный интеллект. малоресурсные языки. Машинное обучение. Разработка мобильных приложений.. ios. Natural Language Processing. nlp. rag. silero. Swift. искусственный интеллект. малоресурсные языки. Машинное обучение. Разработка мобильных приложений. Разработка под iOS.. ios. Natural Language Processing. nlp. rag. silero. Swift. искусственный интеллект. малоресурсные языки. Машинное обучение. Разработка мобильных приложений. Разработка под iOS. семантический поиск.. ios. Natural Language Processing. nlp. rag. silero. Swift. искусственный интеллект. малоресурсные языки. Машинное обучение. Разработка мобильных приложений. Разработка под iOS. семантический поиск. синтез речи.. ios. Natural Language Processing. nlp. rag. silero. Swift. искусственный интеллект. малоресурсные языки. Машинное обучение. Разработка мобильных приложений. Разработка под iOS. семантический поиск. синтез речи. эмбеддинги.

Личная библиотека для книг, статей, подкастов и видео: транскрипция, синтез, перевод и смысловой поиск считаются на самом телефоне.

В прошлых статьях я рассказывал про локальный переводчик с синтезом голоса на кабардинском и про то, как собрать полный NLP-стек для «финального босса лингвистики» из старых словарей. За прошедшие месяцы весь этот стек – лемматизация, POS, NER, тональность, эмбеддинги, синтез – переехал с Python/Flask на десктопе в Swift-приложение на iOS и заодно перерос сам себя: начиналось всё как синтезатор речи для одного языка, а выросло в персональную библиотеку для любого контента – книги, статьи, подкасты, видео, музыка, файлами или ссылками. Полгода на голом Swift 6, вечерами и выходными.

Приложение уже можно ставить и ломать – оно в бете, включая рабочий CarPlay. Под катом: что оно умеет, как устроено внутри и какие цифры получились на реальном железе.

Коротко: что это и как попробовать

Личная библиотека для любого контента с озвучкой, переводом и смысловым поиском. «Офлайн» здесь не про то, что приложение не ходит в сеть – ссылки, подписки и загрузка контента работают как обычно, – а про то, где происходит обработка: распознавание речи, синтез, перевод и весь смысловой анализ считаются на самом телефоне.

Источником может быть файл, ссылка на статью, выпуск подкаста или видео – или целая подписка: RSS новостного сайта, лента подкаста, YouTube-канал. Статья приходит уже очищенной от рекламы и баннеров, одним текстом; и текст, и транскрипт остаются в вашей библиотеке навсегда, даже если издатель удалит оригинал или пропадёт интернет.

Требования. iOS 18.6+, iPhone 12 и новее. Apple Intelligence требует iPhone 15 Pro и новее – ограничение Apple; на старых моделях работает языковой пакет перевода и чат с цитатами, но без перефразирования локальной моделью.

Вес. Приложение – около 50 МБ. Остальное по требованию: Silero-синтез для языков СНГ – 100 МБ, ударения для славянских – 50 МБ, латышский голос – 60 МБ, пунктуация для русского – 80 МБ.

Цена. На время беты всё бесплатно; после релиза один объект с полным ИИ-анализом останется бесплатным навсегда, дальше – поштучно или подписка.

Ссылка: https://sayfable.com/ – тестирование анонимное, отдельный аккаунт не нужен, только TestFlight.

Почему такая библиотека стала возможна именно сейчас

Раньше на телефоне каждый тип контента жил в своём приложении: музыка в одном, подкасты в другом, YouTube в третьем, статьи – если повезёт, в read-it-later вроде закрывшегося Pocket. У аудио нет текста, у текста нет озвучки: нужен транскрипт подкаста – платите за API, хотите спросить у модели, о чём книга, – грузите её в чат-бот и тратьте сотни тысяч токенов, причём результат разбора нигде не сохранится.

С появлением в iOS 26 доступа к локальной языковой модели на уровне системы мы смогли соединить все типы контента в одном приложении и выполнять всю обработку медиа локально на устройстве. Контекстное окно у FM-модели Apple небольшое, но в связке с собственной разметкой этого хватает, чтобы отвечать на вопросы по личной библиотеке без галлюцинаций и со ссылкой на источник. SayFable обогащает любое медиа текстом (транскрипцией или субтитрами), локально размечает и режет на сцены – дальше с ним можно работать как с индексированным текстом: выбрать только интересные сцены из подкаста, собрать из десятка статей абзацы по своей теме, слушать книгу плейлистом сцен с одним персонажем.

Что можно делать в приложении уже сейчас

Импорт книг почти в любом формате. EPUB, FB2, DOCX/DOC, RTF, TXT, PDF – файл разбирается на главы по оглавлению, а внутри главы текст режется на сцены по смыслу.

Карточка книги, которую можно листать во время прослушивания. Не останавливая воспроизведение, видно оглавление: сколько в главе сцен и как они называются – по названиям обычно уже понятно, о чём глава. Рядом «Атмосфера» со статистикой: объём, доли частей речи, соотношение действия и описания, средняя важность и эмоциональность, доля диалогов, типы сцен и эмоциональная кривая по главам. И «Сущности» – персонажи, места и организации из текста; там же персонажу назначается свой голос (по умолчанию вся книга читается одним). Единственное исключение – YouTube: его встроенный плеер останавливается, как только уходишь с экрана видео, и это политика Google, а не наша недоработка.

Карточка книги, экран «Атмосфера» со статистикой и редактор сущностей

Карточка книги, экран «Атмосфера» со статистикой и редактор сущностей

Слева направо: карточка книги с главами и сценами, «Атмосфера» со статистикой разбора и редактор сущностей – 52 персонажа с числом упоминаний, где каждому назначается голос.

Своя подборка по любой теме. Объект в библиотеке – не обязательно одна книга. Можно начать с одной понравившейся статьи и собирать вокруг неё свою тему – робототехника, спорт, что угодно: кнопка «добавить по ссылке» кладёт каждую новую статью, выпуск подкаста или видео с субтитрами отдельной главой. Дальше по всей подборке работают поиск, слайдер важности, озвучка и чат.

Текст к любому аудио – и обратно. Подкаст, видео, свою запись можно распознать в текст и слушать с подсветкой слов. А заменив голос подкаста на любого доступного спикера, вы слушаете речь, синтезированную уже из распознанного текста, – интернет при переслушивании не нужен.

Перевод и дубляж на лету. Вместо оригинала – перевод, вплоть до синтезированной озвучки поверх оригинальной дорожки: закадровый перевод, собранный на телефоне без платного API.

YouTube без рекламы. Видео играет внутри приложения, ниже – субтитры, которые можно переводить и озвучивать. Плеер Google останавливается при блокировке экрана, поэтому есть кнопка «Не блокировать экран»: автоблокировка выключается, яркость гаснет до минимума – телефон можно убрать в карман.

Показать в статье все режимы не выйдет – она и так на пределе размера. Ниже только два кадра, а разборы остального, с видео и скриншотами по каждому экрану, лежат на sayfable.com: кому интересно, там видно куда больше, чем можно втиснуть в текст.

Как это считается на телефоне: таблица вместо векторного чёрного ящика

Чтобы навигация по сценам, плейлисты и чат работали быстро и офлайн, обычного RAG поверх эмбеддингов не хватило. Стандартный подход – нарезать книгу на чанки, заэмбеддить, искать по косинусу – наследует две проблемы: вектор одной модели ничего не значит для другой, а голое косинусное сходство у контекстных эмбеддингов регулярно промахивается мимо смысла (анизотропия – «всё похоже на всё» с косинусом около 0.9, пока вектор не отцентрировать).

SayFable вместо этого один раз при импорте строит из книги колоночную таблицу – плоские массивы «одна колонка на признак», по которым можно сканировать без разбора JSON. Уровней три, различаются они гранулярностью: слово → предложение и абзац → сцена.

Слово. Лемма, часть речи, номера предложения / абзаца / сцены, позиция ударной гласной и salience – частота слова внутри своей сцены, умноженная на обратную частоту по сценам книги: «насколько слово необычно вот здесь», а не «насколько оно редкое вообще». Отдельной колонкой – эмоциональная валентность из словаря Уорринера (для трёх десятков языков перенесена на леммы через глоссы тем же мостом, что описан во второй статье).

Предложение и абзац. Реплика это или авторская речь, вопрос, восклицание, кто говорит, доля диалога, плотности частей речи. И здесь же – важность, которую крутит слайдер:

  • важность предложения = сумма salience его знаменательных слов с фиксированным весом части речи (существительное 1.0, глагол 0.9, прилагательное 0.7), умноженная на центральность предложения к центроиду своей сцены – центрированный косинус его эмбеддинга. Именно умножение: набор редких слов в предложении, к теме сцены отношения не имеющем, важности не даёт;

  • важность абзаца = среднее по предложениям. Красивая гипотеза «взять топ-K лучших и пик» проиграла бенчмарку на всех трёх тестовых книгах, так что в коде осталось скучное среднее;

  • дальше значение ранжируется внутри главы в перцентиль и подгоняется под эталонный профиль. Без этого абсолютные пороги («важность > 0.75» для интонации кульминации) значили бы в разных книгах разное: на одной срабатывали бы на каждом абзаце, на другой – никогда.

Целиком закон важности выглядит скучнее своего описания – и это, пожалуй, лучшее, что можно про него сказать:

static func posWeight(_ p: POSCode) -> Double {
    switch p {
    case .noun, .propn: return 1.0
    case .verb:         return 0.9
    case .adj:          return 0.7
    case .num:          return 0.5
    default:            return 0.2
    }
}

/// Масса значимости предложения: Σ salience × вес части речи.
static func salienceMass(_ lemmas: [WeightedLemma]) -> Double {
    lemmas.reduce(0) { $0 + $1.salience * $1.posWeight }
}

/// Центральность к центроиду сцены. Нет вектора — нейтральная 1.0.
static func centrality(_ v: [Float]?, _ centroid: [Float]?) -> Double {
    guard let v, let centroid else { return 1.0 }
    return max(0, cosine(v, centroid))
}

static func sentenceScore(salienceMass: Double, centrality: Double) -> Double {
    salienceMass * centrality
}

Ни одной обучаемой величины, ни одного вызова модели – чистая арифметика по колонкам, поэтому она одинаково считается и на телефоне, и в Mac-утилите, которая готовит .say.

Сцена. Границы считаются по сдвигу плотностей частей речи и по семантическому разрыву эмбеддингов – не по длине абзаца и не по одному сигналу. Дальше сцена получает свёрнутые сверху метрики: средняя и пиковая важность, эмоция и её размах, доля диалога, доминирующий говорящий, тип сцены. Арифметика по готовым колонкам, никакой модели.

Где нужна модель побольше. Локальная Foundation Model от Apple подключается точечно и только на верхнем уровне: назвать сцену заголовком в шесть слов и разметить её паспорт – точка зрения, место, время, ключевые события. Принципиальный момент: модель не пишет прозу, которая потом хранится и переигрывается, – она возвращает ссылки на предложения самой книги. Поэтому чат отвечает цитатами из текста: галлюцинировать нечем, максимум промахнуться ссылкой. Честно про потолок: разметка сущностей и говорящих на телефоне пока эвристическая, без валидации большой моделью – точность 80–90%, список сущностей иногда приходится править руками.

На эту же таблицу опираются слайдер важности от 5% до 100% (прокрутить книгу, оставив только несущий сюжет), чат с цитатами вместо пересказа и синтез: колонки «говорящий» и «эмоция» отдаются озвучке, поэтому у персонажа свой голос, а темп и высота меняются вместе с тем, что происходит в предложении. Как эта таблица выглядит глазами пользователя – на скриншоте иммерсивного режима ниже: там у каждого слова видны ровно эти колонки.

Что это значит в цифрах

Всё мерилось на iPhone 15 Pro Max, на релизной сборке и на реальных книгах — цифры ниже это время стены из логов, а не оценка.

Операция

Замер

В пересчёте

Полный анализ книги – разметка, эмбеддинги, сцены

2069 абзацев, 32 главы – 2,5 минуты

около 85% этого времени уходит на фазу эмбеддингов

Транскрипция аудиокниги

11 ч 24 мин звука – 50 минут

≈14× быстрее реального времени

Перевод на языковом пакете Apple

6,5 абзаца в секунду

книга в 3400 абзацев – минут за девять

Синтез речи (самый тяжёлый процесс)

9,7–10,3× быстрее реального времени

Apple и Silero на одном тексте практически поровну

Ждать всё это целиком не надо: и транскрипция, и синтез идут по главам, так что слушать можно, пока готовятся следующие. А вот анализ стоит первым в очереди не случайно – он дешевле всего остального на порядок и при этом обязателен: из его колонок синтез берёт, где ускориться, где понизить тон и где поставить паузу. Без него озвучка получается ровной и плоской, каким бы хорошим ни был голос.

Эти цифры держатся на сознательном ограничении: всё построено на инфраструктуре самой Apple. Распознавание речи, перевод, эмбеддинги, языковая модель, аудиосессия, CarPlay – системные фреймворки, а не сторонние библиотеки. Извне мы тащим только данные: словари для разметки и веса моделей синтеза. Единственная внешняя зависимость с кодом – ONNX Runtime, нативный бинарник с предсказуемым поведением, который мы вызываем в своём потоке. Держать приложение, часами синтезирующее речь в фоне и при этом не падающее, сложно и так – каждая лишняя зависимость делает это на порядок сложнее.

Отсюда же честный ответ про Android: там нет системной локальной модели такого уровня, и половине стека просто не на чем стоять. Портировать урезанную копию мы думаем, но опыт может выйти хуже, чем отсутствие приложения.

Два железных ограничения – и обходные манёвры

Перевод через Apple Intelligence. Когда он включён, система сама перехватывает перевод и гонит его через генеративную модель – на уровне ОС, вне контроля приложения. Замер на книге в 3431 абзац: с включённым Apple Intelligence – 0.32–0.56 абзаца в секунду и тепловая пауза каждые 50–100 абзацев; с выключенным, через обычный языковой пакет – 5.9–6.5 абзаца в секунду и одна пауза на всю книгу. Разница больше чем десятикратная, и скачанный языковой пакет при включённом Apple Intelligence просто игнорируется. Обходной путь нашёлся не в коде, а в настройках телефона: на время большого перевода Apple Intelligence отключается целиком.

CarPlay и живой синтез. Прямое воспроизведение синтезированной речи в машине заикалось: CarPlay не готов к живому потоку TTS, а перезапуск плеера на границе каждого абзаца слышно растягивал паузы. Решение двухэтажное: аудио сначала синтезируется в файл и играет из буфера, а готовые файлы соседних абзацев одной сцены склеиваются в бесшовный трек. Цена – задержка 2–3 секунды на старте ещё не подготовленной главы.

CarPlay: своя поверхность со списком «Недавние / Библиотека / Подписки / Плейлисты»

CarPlay: своя поверхность со списком «Недавние / Библиотека / Подписки / Плейлисты»

 В машине это отдельная поверхность с недавним, библиотекой, подписками и плейлистами, а не один экран «сейчас играет».

Малые языки: то, ради чего всё начиналось

Здесь сходятся все три статьи. Всё, что было в первых двух на примере кабардинского – синтез Silero, словарная лемматизация и POS-разметка, перенос тональности через языковой мост, — оказалось не разовым случаем, а методом: тем же путём полный словарный стек доведён до покрытия у 19 из 20 языков с Silero-озвучкой. Раньше это жило Python-сервисом на Mac mini, теперь работает на телефоне.

Ядро – портированная на iPhone Silero base под лицензией MIT: 43 голоса на 20 языков, полностью офлайн. По группам: славянские (русский, украинский, белорусский – с ударениями), тюркские (восемь языков), финно-угорские (три), кавказские (кабардинский, грузинский, армянский), плюс таджикский и калмыцкий. Ударения ставит отдельная портированная модель – русский без правильных ударений это не отдых, а наказание для слушателя. Ещё одна закрывает похожую дыру в распознавании: Apple прекрасно распознаёт русскую речь, но не расставляет знаки препинания (замер модели пунктуации против авторских субтитров подкаста: совпадение знаков 88.5%, капитализации 93.6%). Нативную лемматизацию, POS и NER от Apple из этих 20 языков получает только русский; для остальных 18 всё собрано словарным методом из второй статьи.

Масштаб в цифрах: не только Silero

Silero – только один слой. Синтез, распознавание речи, перевод и NLP-разметка считаются отдельно, и покрытие у них разное:

Функция

Языков и вариантов

Основные группы

Синтез речи (Apple + Silero + Piper)

68

европейские, славянские, тюркские, финно-угорские, кавказские, азиатские

Распознавание речи

47

европейские, славянские, азиатские

Машинный перевод

22

европейские, славянские, азиатские

Полный NLP-стек (лемма + POS + NER + тональность)

52

германские, романские, славянские, тюркские, финно-угорские, кавказские

Полный список кодов – на сайте проекта. Отдельная история – эмбеддинги для смыслового поиска: у Apple они привязаны к письменности, а не к языку, поэтому латиница и кириллица считаются напрямую, а для белорусского, греческого, армянского, грузинского и кабардинского не работает ни та, ни другая модель. Там включается мост через глоссы: предложение слово за словом переводится в русские или английские глоссы, и эмбеддинг считается уже по ним. Каждый такой маршрут включался после замера с контрольным языком, а не «на всякий случай». Словари и модели для «обделённых» языков выложены отдельно, с открытыми лицензиями: github.com/sayfable-models.

Иммерсивный режим: побочный эффект полезнее исходной идеи

Планировался он как окно в то, как модель видит текст: у каждого слова лемма, часть речи, сущности, вес важности, валентность и настройки синтеза. Просто показать двигатель изнутри.

Но стоило подставить в то же окно глоссу – тот самый мост из предыдущего раздела, – как получился режим чтения на другом языке: под каждой леммой не перевод всей фразы, а её собственный аналог.

Иммерсивный режим: часть речи, кабардинская глосса, параметры синтеза, важность, эмоция и говорящий

Иммерсивный режим: часть речи, кабардинская глосса, параметры синтеза, важность, эмоция и говорящий

 «Остров сокровищ» с кабардинскими глоссами: у текущего слова – часть речи, глосса ар, езы, рассчитанные rate / pitch / volume / pause и голос говорящего, ниже – важность, эмоция и сцена абзаца. Это и есть та самая таблица, показанная человеку.

Насколько это педагогически полезно – вопрос открытый: я читаю английские слова с параллельными кабардинскими глоссами, и они запоминаются заметно легче. Возможно, это особенность моего восприятия, а не свойство метода, – интересно, работает ли это у кого-то ещё.

Формат .say: разбор, который не приходится пересчитывать

.say – книга вместе с уже посчитанной таблицей: весами слов и валентностью, настройками синтеза, транскрипцией, сущностями, эмбеддингами предложений, переводом. Разобрали один раз – переслали файл на другой телефон, и там ничего не пересчитывается: книга открывается со сценами и персонажами сразу. 600 страниц весят около 15 МБ вместе с исходным текстом – в 50 раз меньше средней аудиокниги. Открытую спецификацию опубликуем на GitHub после тестов.

Отсюда же план, который снимает оговорку про эвристику выше: издавать книги из общественного достояния сразу в .say, с разметкой, прогнанной на большой модели. Тогда не придётся ни ждать анализа, ни править сущности руками: у каждой реплики проставлен правильный персонаж, а границы сцен подтверждены сменой времени, места и состава действующих лиц, а не только статистикой.

Как помочь с тестированием

Движок, кажется, получился. А удобное приложение рождается только из того, как им пользуются живые люди, – как любому родителю, мне сложно увидеть недостатки своего детища. Что интереснее всего проверить:

  • Заменяет ли SayFable у вас несколько отдельных приложений – или удобнее держать их порознь.

  • Синтез и подбор голоса под персонажа; для славянских – качество ударений и помогает ли личный словарь исправлений.

  • Насколько осмысленно собираются плейлисты по важности, эмоции и персонажам на вашей книге.

  • Ловит ли чат себя за руку там, где обычный чат-бот тихо бы что-то придумал: совпадают ли цитаты с текстом.

  • Как ведёт себя CarPlay – есть ли заикания на стыках абзацев на ещё не подготовленных главах.

  • Если вы носитель малоресурсного языка – как звучит синтез и насколько адекватна разметка.

Ссылка: https://sayfable.com/ – тестирование анонимное. Буду рад любым отзывам, восторженным и не очень.

Автор: Kubataba

Источник