Навіщо мені це знадобилося
Ні, усе почалося не з транскрибування мітингів — ними закінчилося.
Спершу мені потрібен був інструмент, який я запускаю — і далі просто коментую вголос усе, що роблю із застосунком на екрані.
Скажімо, тестую якийсь застосунок і кажу: «Подивімося, що буде, якщо я натисну цю кнопку. О, форма. А що, як я введу сюди ось це значення? Ха, не спрацювало, пише помилку. Ну гаразд, а що буде, якщо я виділю он ту опцію?..»
Далі я хотів згодовувати LLM такі надиктовані сесії, умовно на годину завдовжки, разом зі знімками екрана й отримувати на виході корисний документ або набір документів. А якщо трапляться баги — майже готові JIRA-тікети: контекст, кроки відтворення, очікувана й фактична поведінка, скриншот. Маючи knowledge base проєкту та вихідний код, LLM доволі непогано розібралася б, що я коментував, дивлячись на інтерфейс.
Другий сценарій — туторіали. Надиктувати простіше, ніж написати. Якщо сказане саме перетворюється на текст, туторіал на будь-яку тему виходить із мінімальними зусиллями: я просто показую й розповідаю, а потім віддаю це моделі на редагування.
Але врешті 9 із 10 застосувань інструмента — це транскрипти мітингів і допомога в тому, щоб ефективніше працювати під час дзвінка.
Забігаючи наперед, скажу, що ще виявилося дуже зручно перетворювати лекції з YouTube на читабельні статті зі скриншотами й врізками.
Для розігріву — ось який вигляд має фінальний результат: документ, зібраний із запису доповіді з YouTube. В автоматичному режимі це працює за принципом «увімкнув відео й забув»: інструмент сам слухає й сам вибирає кадри, а документ збирається однією кнопкою.
Далі в статті йдеться переважно про консольну версію: вона і є ядром інструмента. Поверх неї я нещодавно зробив вебінтерфейс (./webui.sh), і відео та скриншоти нижче знято вже в ньому. Інтерфейс запускає той самий main.py і додає живий транскрипт, панель скриншотів, вибір джерела звуку, області знімання (увесь екран, область або конкретне вікно) і моделі розпізнавання, автоматичні кадри, обробку транскрипту кнопкою Beautify і чат щодо запису, що ще триває. А тепер той самий інтерфейс є й у вигляді застосунку для macOS, з усім потрібним усередині, — про нього наприкінці.
Beautify і чат працюють через Claude Code або через API LLM, на вибір: Gemini, OpenAI, Anthropic, OpenRouter чи будь-який OpenAI-сумісний сервер, зокрема локальні Ollama й LM Studio. З хмарною моделлю ця частина, на відміну від розпізнавання, не локальна.
Ось як це виглядає наживо — запис доповіді з YouTube, скриншоти слайдів за хоткеєм і обробка транскрипту:
Так от, ми згадали, що тут не лише аудіопотік, а й на екрані щось відбувається, і постає питання
Коли знімати екран
І як потім вбудувати релевантне з екрана в майбутній текст? Одна картинка варта тисячі слів, і без неї ні баг-репорт, ні туторіал не працюють.
Спочатку я мріяв, щоб система сама якось розуміла, який кадр залишити, бо він дуже доречно лягає в контекст тексту. Я б увімкнув ролик на YouTube, залишив комп'ютер його слухати й повернувся б до готового документа, що описує зміст ролика, зі скриншотами-кадрами. Довго здавалося, що поки що зарано. Виявилося, не зовсім: тепер в інструменті є й автоматичний режим, про нього нижче. Але почнімо з ручного — він з'явився першим і залишається основним.
Записувати відео повністю — надмірно: потім однаково доведеться якось шукати потрібні кадри, а як до цього підступитися, поки незрозуміло. Значно краще, коли людина сама вирішує, який кадр ключовий, адже вона однаково сидить і слухає. Так з'явилася ідея глобального хоткея: натиснув — знімок екрана збережено, а в транскрипт тієї ж миті вставлено маркер [screenshot: шлях]. Текст і картинка пов'язані в часі, і LLM потім точно знає, до якої фрази належить кадр.
Несподівано: воно чує й динаміки
Згодом я зрозумів, що ця штука через мікрофон чудово «чує» те, що «промовляє» динамік. Я цього зовсім не очікував. Мені здавалося, що мікрофон ноута якось відфільтровує те, що лунає з динаміка. Але — працює, і дуже надійно. І одразу з'явився цілий клас застосувань.
- YouTube і будь-які відео. Вмикаєш ролик, час від часу тиснеш хоткей на важливих слайдах — і на виході маєш транскрипт із вбудованими скриншотами.
- Дзвінки в Teams. За моїми спостереженнями, розпізнає навіть краще, ніж сам Teams.
Є й зворотний бік: через мікрофон усе це працює, лише поки звук іде з динаміків. У навушниках чи гарнітурі мікрофон співрозмовника не почує, і в транскрипт потрапить лише ваше мовлення. Тому тепер звук дзвінка можна брати й напряму, про це нижче.
Звідси ж випливає, що найкраще він працює вдома, у тихій кімнаті. В open space я його не перевіряв. Найімовірніше, детектор мовлення пропускатиме на розпізнавання чужі розмови. Частину такого сміття LLM прибере на постобробці, бо воно не в'яжеться з темою дзвінка, але розраховувати на це я б не став.
Вбудований транскрипт Teams має кілька незручностей і зручностей:
| Teams | Мій інструмент | |
|---|---|---|
| Коли доступний текст | Лише після завершення дзвінка | Одразу, під час розмови |
| Хто може завантажити текст | Наскільки я розумію, тільки той, хто записував; решті — інтерактивний перегляд, звідки нормально не скопіюєш | Звичайний .txt у мене на диску |
| Ключові кадри | Немає — зберігається все відео цілком | Скриншоти за хоткеєм, просто в тексті |
| Хто що сказав | Знає точно | Опційна діаризація за голосом, без імен; імена можна взяти з транскрипту самого Teams |
Останній пункт — єдиний, де Teams безперечно виграє. Але тепер і його можна забрати собі: транскрипт Teams завантажується в проєкт як друге джерело, про це нижче. А витрачати годину на перегляд годинного дзвінка шкода. Хочеться транскрипт і ключові кадри.
Звук дзвінка — напряму
Слухати динаміки через мікрофон виявилося зручно, але в навушниках це не працює. Тому в інструменті з'явився вибір джерела звуку — прапорець --source у консолі або список Source у вебінтерфейсі:
| Джерело | Що слухає | Що в транскрипті |
|---|---|---|
| Microphone (за замовчуванням) | Мікрофон, як і раніше | Усе впереміш |
| App + mic | Звук одного застосунку, наприклад Teams, і окремо мікрофон | Рядки з позначками [Them] і [Me] |
| App only | Тільки звук застосунку | Усе, що він відтворював |
| Audio file | Готовий файл: mp3, m4a, wav, відео | Мітки часу — позиції у файлі |
Звук застосунку перехоплює другий нативний хелпер, audio_helper, через Core Audio Process Taps (macOS 14.2+). Звук Teams при цьому, як завжди, іде в навушники, а в транскрипт іде чистий звук дзвінка, без шуму кімнати. Мікрофон і застосунок розпізнаються як два незалежні канали, кожен зі своїм детектором мовлення, тож репліки не злипаються, навіть коли говорять одночасно. А питання «хто що сказав» одразу наполовину розв'язане: [Me] — це я, розділяти за голосами треба лише [Them].
Є й підводні камені: Teams відтворює звук не основним процесом, а допоміжним ModuleHost, тому хелпер групує процеси за застосунком. А macOS просить окремий дозвіл — System Audio Recording.
Режим Audio file — для запису, який надіслали після зустрічі: він проходить тим самим конвеєром, що й живий, тільки не в темпі реального часу, а так швидко, як устигає розпізнавання. Скриншотів і кадрів у цьому режимі, зрозуміло, немає.
Один вечір і тільки локальні моделі
Усі ці міркування привели до того, що буквально за один вечір я зробив зручний для себе інструмент. Ну гаразд, не я — ми з Claude Code. А потім ще кілька місяців час від часу додавав нові функції й виправляв помилки — що з цього вийшло, описано далі.
Важливіше інше: розпізнавання працює лише на локальних моделях. Звук ваших дзвінків і скриншоти вашого екрана нікуди не йдуть, і це величезний плюс. Постобробка через LLM опційна, і навіть для неї я зазвичай використовую локальні моделі.
З чого складається продукт:
- Розпізнавання мовлення — одна з трьох моделей на вибір, усі на CPU з квантизацією int8: faster-whisper з
large-v3-turbo(за замовчуванням), GigaAM v3 для російської або Parakeet v3 для суміші російської та англійської. Докладніше про них нижче. - Детектор мовлення — Silero VAD. Розпізнавання запускається лише на фрагментах, де хтось говорить; фраза вважається завершеною після 1,5 с тиші.
- Автовизначення мови — російська чи англійська, перша перевірка після 30 слів, далі повторна перевірка кожні 200 слів.
- Нативний хелпер на Swift — реєструє глобальні хоткеї macOS і знімає екран. З Python він спілкується просто через stdout.
- Хелпер для звуку застосунків — другий бінарник на Swift: перехоплює звук вибраного застосунку через Core Audio Process Taps і теж віддає його через stdout.
- OCR (оптичне розпізнавання тексту) — EasyOCR, російська та англійська.
- Розподіл за спікерами (опційно) — ембединги голосу SpeechBrain ECAPA-TDNN і кластеризація. Дані сесії зберігаються, тож розмітку можна перерахувати згодом з іншими параметрами, не перезаписуючи.
Усе це крутиться в кількох потоках одночасно, тож текст з'являється у файлі практично в реальному часі, з мітками [ГГ:ХХ:СС].
Одразу застережу: оцінки швидкості та якості в статті — мої особисті спостереження з досвіду використання. Систематичних вимірювань я не проводив. Винятки — порівняння моделей розпізнавання та цифри щодо автоматичних кадрів нижче.
Мовлення і скриншоти проходять двома незалежними конвеєрами й зустрічаються в транскрипті через маркери; LLM отримує обидва потоки одразу.
Скриншоти з OCR і вставлення виділеного тексту
Інструмент має два глобальні хоткеї, які працюють у будь-якому застосунку:
| Хоткей | Що робить | Що потрапляє в транскрипт |
|---|---|---|
| Ctrl+Shift+S | Зберігає екран у PNG і запускає OCR; увесь розпізнаний текст лягає в .txt поруч із картинкою | [screenshot: шлях до PNG] |
| Ctrl+Shift+W | Бере текст, виділений у будь-якому вікні | [selection: сам текст] |
Далі OCR-файл слугує довідником під час постобробки. Імена людей, бренди, назви класів і файлів на екрані написано правильно, і LLM виправляє за ними те, що розпізнавання почуло неправильно. Це помітно надійніше, ніж розпізнавати такі речі лише з аудіо.
Сполучення зашиті в хелпері (screenshot_helper.swift), і перепризначити їх поки можна лише в коді. Знаю, що Ctrl+Shift+W у браузерах на Windows і Linux закриває вікно, тож для портів сполучення доведеться обирати наново, а краще винести їх у налаштування.
Вставлення виділеного тексту зручне в режимі диктування. Кажеш уголос «а ось це список продуктів компанії», виділяєш список на сайті й тиснеш хоткей. Список потрапляє в текст дослівно, без помилок розпізнавання.
Ще кілька можливостей:
- Проєкти. Кожен запис іде у свою теку
projects/<ім'я>/із транскриптами, скриншотами й OCR-файлами. - Запис звуку. З прапорцем
--save-audioсесія додатково зберігається в MP3. - Спікери. Прапорець
--speakers 2або--speakers autoвмикає розмітку «хто говорить», аrediarize.pyперераховує її за збереженими даними.
Автоматичні кадри
Натискати хоткей на кожному слайді втомливо, а на довгій доповіді неодмінно щось пропустиш. Тому в інструменті з'явився другий режим — Auto frames. Вмикається перемикачем перед записом, кнопкою Auto під час запису або прапорцем --auto-frames 1 у консолі.
Ідея проста: знімати екран щосекунди й залишати тільки те, що варто залишити. Проганяти модель через кожен кадр марнотратно — у презентації 95% сусідніх кадрів однакові, і це видно за мілісекунду. Тому кадри проходять каскад, від дешевого до дорогого:
- Пікселі. Кадр зменшується до 256 px у відтінках сірого й порівнюється з попереднім. Кандидатом він стає не тоді, коли екран змінився, а коли після зміни простояв нерухомо 1,5 с. Тож переходи між слайдами, прокручування в процесі й набір тексту кандидатами не стають ніколи — стає кінцевий стан. Голова, що говорить, нерухомою не буває й відсіюється сама. Якщо весь час рухається тільки один прямокутник (вебкамера доповідача поруч зі слайдами), він закривається маскою й не заважає.
- Сцени. Кандидат відкриває нову сцену, якщо відрізняється від поточної більш ніж на 3% площі або якщо змінилося понад половину вмісту слайда: на темному слайді новий заголовок займає лише кілька відсотків кадру. Повернення до недавнього слайда записується як повтор, без нової картинки; прокручена сторінка позначається як прокручування.
- CLIP. Сюди доходять тільки кандидати, кілька на хвилину. Модель CLIP рахує ембединг картинки (14 мс на M3 Max) і без навчання визначає тип кадру: слайд, схема, таблиця, код, вебсторінка, обличчя. Кадр, де переважно обличчя або відеодзвінок, викидається. Цей рівень можна вимкнути в налаштуваннях, тоді лишаються тільки пікселі.
Зберігається не скриншот, а сцена: кадр плюс інтервал, коли він був на екрані. Сцени лежать поруч із транскриптом у frames/<сесія>/ і в сам файл транскрипту не пишуться; з мовленням вони зіставляються за часом. У вебінтерфейсі вони показуються просто в транскрипті, там, де з'явилися, з позначкою «auto».
Тут я вимірював, на одному записі доповіді: 11,5 хвилини, слайди з вебкамерою в куті й лазерною указкою.
| Що | Результат |
|---|---|
| Кадри → сцени | 692 → 10 за 2 секунди, жоден слайд не пропущено |
| Той самий слайд 4 хвилини, по якому водять указкою | Одна сцена |
| Вебкамера, розтягнута на весь екран на 2 хвилини | Пікселі пропустили 2 кадри з обличчям, CLIP викинув обидва |
Коли відео — лише частина вікна
Якщо знімати не весь екран, а вікно браузера чи Teams, більша частина кадру — нерухомий текст сторінки, а відео — один прямокутник. Тоді зміна слайда тоне в сторінці: темний слайд із парою слів змінює менше ніж 3% вікна, і слайди зливаються.
Для цього в області знімання є кнопка Detect video. Інструмент кілька секунд дивиться на вікно й знаходить плеєр за несподіваною ознакою: декодоване відео навіть на нерухомому слайді трохи мерехтить, на рівень-два яскравості, а намальована браузером сторінка нерухома до біта. У моїх тестах усередині плеєра змінювалися 3–8% пікселів, назовні — рівно 0%. Якщо рамку плеєра видно, кроп прилипає до неї; якщо ні (темний слайд на темній сторінці), область розширюється до пропорцій відео із запасом. Відео на паузі знаходиться за самою рамкою.
Кроп малюється на прев'ю, і його можна пересувати й розтягувати мишкою. Діє він тільки на автоматичні кадри: звичайні скриншоти, як і раніше, знімають вікно цілком.
Кадрів сотні, потрібно десять
Навіть після каскаду сцен лишаються десятки, і не всі вони потрібні в документі. Тому будь-яку репліку транскрипту, будь-який скриншот і будь-який кадр можна приховати кнопкою з оком. Прихована репліка лишається на місці, але блідне; картинка стає сірою. У Beautify іде все неприховане, зокрема автоматичні кадри — як скриншоти, на свій час.
Приховувати по одному довго, тому поруч з оком є друга кнопка — «цю й схожі». Для реплік вона приховує такі самі або майже такі самі фрази — зручно для галюцинацій Whisper і нескінченних «угу». Для картинок — усі скриншоти й кадри, схожі на вибраний, за ембедингом CLIP: та сама голова, що говорить, у різні моменти давала 0,93 і вище, різні слайди однієї доповіді — 0,84 і нижче. Помилковий клік скасовується з повідомлення.
Грубий відбір робить кнопка Pick у панелі кадрів. Вона залишає N кадрів, про які найбільше говорили, а решту приховує. Найсильніший сигнал — вказівні слова в мовленні, поки кадр був на екрані: «ось тут», «як бачите», «на цій схемі»; далі йдуть час на екрані й обсяг мовлення. Який кадр важливий, вирішує транскрипт, а не картинка.
І ще одна зміна принагідно: фінальний Beautify після запису більше не запускається сам. Спершу він питає, а якщо відповісти «не зараз», кнопка Beautify лишається на потім.
Як це влаштовано всередині
Головний принцип — запис ніколи не чекає на розпізнавання. Звук пишеться асинхронно, а вся важка робота відбувається в окремих потоках, пов'язаних чергами.

| Потік | Що робить | Кому передає |
|---|---|---|
| Захоплення звуку | Мікрофон і/або звук застосунку від audio_helper: 16 кГц, моно, блоки завдовжки 100 мс |
Черга сирого звуку; у режимі App + mic — окрема черга й окремий детектор мовлення для кожного каналу |
| Детектор мовлення (VAD) | Silero, вікна завдовжки 32 мс; фраза закривається після 1,5 с тиші (--pause), уривки, коротші за 250 мс, відкидаються |
Черга на розпізнавання: звук фрази + час її початку |
| Розпізнавання | faster-whisper (beam search 5), GigaAM або Parakeet, фраза за фразою | Транскрипт на диску, запис одразу після кожної фрази |
| Хелпер на Swift + читач його виводу | Хоткеї, PNG, виділений текст | Черга маркерів і черга OCR |
| OCR | EasyOCR для кожного скриншота | .txt поруч із PNG |
Ембединги голосу (з --speakers) |
ECAPA-TDNN для коротких фраз | Дані сесії для кластеризації наприкінці |
Розбір кадрів (з --auto-frames) |
Порівняння пікселів, потім CLIP для тих, що лишилися | frames/<сесія>/scenes.json і JPEG сцен |
Розпізнавання відстає, але наздоганяє. Whisper отримує фразу лише після паузи, і на CPU йому потрібен час. Фрази накопичуються в черзі, а під час кожної паузи в розмові розпізнавання наздоганяє. У типовому записі пауз вистачає, тому текст з'являється практично в реальному часі. Довгий монолог без пауз піде на розпізнавання одним шматком і з'явиться пізніше — за відчуттями, секунд за 30. Усе це про Whisper: GigaAM справляється з фразою за частки секунди, і черга практично не накопичується. А у вебінтерфейсі на випадок довгого монологу є кнопка Cut: вона одразу закриває поточну фразу й надсилає її на розпізнавання, не чекаючи на паузу.
Час — за звуком, а не за годинником. Мітку [ГГ:ХХ:СС] обчислюють за номером семпла, з якого почалася фраза. Навіть якщо розпізнавання відстало, мітка показує, коли фразу сказали.
Скриншот потрапляє туди, куди дійде розпізнавання, а не туди, де воно було. Маркер [screenshot: …] чекає у своїй черзі, і у файл його записує той самий потік розпізнавання, між фразами. Тобто маркер лягає не в місце, розпізнане на момент натискання хоткея, а в місце, до якого черга дійде за кілька секунд. Інакше скриншоти вставлялися б зарано: у момент натискання поточна фраза зазвичай ще не розпізнана, а то й не договорена.
Протокол із хелпером — рядки в stdout. Хелпер пише SCREENSHOT:<шлях> або SELECTION:<текст у base64>, Python читає їх в окремому потоці. У зворотний бік, через stdin, ідуть команди: SNAP — зняти скриншот, AUTO 1 — увімкнути автоматичні кадри (JPEG щосекунди, у відповідь FRAME:<шлях>), AUTO OFF — вимкнути. Виділений текст хелпер бере, імітуючи Cmd+C і читаючи буфер обміну через 150 мс, тож уміст буфера перезаписується. У консольному режимі за замовчуванням скриншот знімається з основного монітора; у вебінтерфейсі можна вибрати область або окреме вікно, причому вікно знімається, навіть якщо його щось перекриває. Протокол audio_helper ще простіший: сирий float32 16 кГц у stdout, у темпі реального часу. Коли застосунок мовчить, ідуть нулі, тож кількість семплів завжди збігається з часом, що минув, і мітки двох каналів не розходяться.
Мова — за часткою кирилиці. До першої перевірки мову кожної фрази визначає сам Whisper. Потім беруться останні 300 слів: від 80% кирилиці — російська, до 20% — англійська, у проміжку мова не змінюється. З GigaAM мова завжди російська: інших він не розуміє.
Спікери — наприкінці сесії. Усі рушії розпізнавання віддають таймстемпи для кожного слова. Слова розрізаються на фрази за паузами, довшими за 0,4 с, або кожні 3 с, короткі доповнюються звуком до 1 с, і для кожної паралельно із записом обчислюється ембединг голосу. Кластеризація відбувається один раз, після зупинки. У режимі App + mic репліки з мікрофона одразу позначаються як [Me] і в кластеризацію не потрапляють. Вихідний текст лишається поруч як .raw.txt, дані сесії — у .pkl для rediarize.py.
Ctrl+C не обриває запис, а завершує його. Система перестає слухати й надсилає недоговорену фразу на розпізнавання. Потім доопрацьовує все, що накопичилося в чергах: розпізнає решту фраз, дописує маркери, закінчує OCR, зберігає MP3 і розмічає спікерів. Так само поводиться автозупинка за таймером.
Від сирого транскрипту до документа
Сирий транскрипт зі скриншотами або з їхніми OCR-текстами далі можна перетворити через LLM на охайний текст або HTML — інтерактивний чи звичайний.
У репозиторії для цього є скрипт process_transcript.py. Він збирає транскрипти за день, діапазон дат або проміжок часу, додає до них OCR скриншотів як довідковий матеріал і просить модель:
- Виправити помилки розпізнавання, звіряючись із текстом з екрана.
- Зіставити знеособлених «Speaker 1», «Speaker 2» зі справжніми іменами, якщо їх видно на скриншотах або вони прозвучали в аудіо в контексті. Тобто якщо Василь попросив Петра прокоментувати, а далі коментує Петро, то система знає, що Speaker 12 — це Петро. А якщо Петро подякував Василеві, то знає й те, що Speaker 7, який просив, — Василь.
- Скласти документ: ключові рішення, таблиця завдань і очищений діалог із таймстемпами.
- Зробити з цього односторінковий HTML із вкладками, пошуком за текстом і фільтром за спікерами.
Документ, що вийшов із запису у відео вище, можна відкрити повністю. Це версія 2: запис у цей момент ще тривав, і модель позначила в callouts, що нового з'явилося порівняно з першою версією. Збирання цієї версії з дев'яти хвилин запису й шести слайдів тривало 1 хв 42 с.

Для нечутливих даних я часто просто віддаю теку проєкту Claude Code. Прошу зберегти всі репліки й додати callouts із важливими моментами — виходить дуже зручно. Для чутливих даних використовую локальні моделі.

Окремо скажу: транскрипти після обробки LLM справді комфортно читати. Модель прибирає шум і сторонні репліки й перетворює текст на доволі близький до того, що потрапляло в мікрофон.
Точним він від цього не стає: галюцинацій ніхто не скасовував. Проте серйозних помилок я поки не помічав, і моделі цілком дають раду.
Транскрипт Teams як друге джерело
Транскрипт Teams і мій мають дзеркальні сильні сторони. Teams точно знає, хто говорить, але перекручує бренди й терміни. У мене навпаки: терміни правильні завдяки OCR і нотаткам проєкту, а спікери безіменні.
Тому транскрипт Teams (.vtt, .docx, .txt або .srt) тепер можна додати на вкладку Notes, і Beautify зводить два джерела: спікерів бере з Teams, терміни й час — із мого, а те, що моє розпізнавання пропустило, відновлює з транскрипту Teams. Чат Ask про цей файл теж знає.
Якщо транскрипту Teams немає, імена можна розставити вручну. Виділяєш фразу в транскрипті й вибираєш «Who said it»: у нотатки проєкту лягає рядок - "цитата" <-- Ім'я (at ГГ:ХХ:СС). Beautify віддає цю репліку названій людині й за нею ж упізнає той самий голос в інших місцях.
Транскрипт + база знань: підказки під час розмови
Цю штуку дуже зручно поєднувати з базою знань на тему розмови. Транскрипт пишеться у файл просто під час розмови, тому його можна обробляти будь-коли, навіть неповним. Віддаєш LLM те, що вже сказано, разом із контекстом і питаєш: які запитання ми ще забули поставити, хоча для них був чи є привід?

Найпростіший приклад — співбесіда, з боку інтерв'юера. Завантажуєш резюме кандидата, і система під час розмови підказує теми для обговорення. Водночас вона враховує обидва джерела одразу:
- Уже обговорене. Кандидат міг згадати досвід, якого в резюме немає, — і система запропонує копнути туди. І не пропонуватиме того, що вже розібрали.
- Саме резюме. Без нього те, що згадав кандидат, можна зрозуміти двояко. З ним модель бачить, на якому проєкті й у якій ролі це було, і може запропонувати уточнювальне запитання.
Те саме працює для збирання вимог (база знань — документація проєкту), дзвінків із продажу (історія клієнта) і тестування (специфікація — які сценарії ще не перевірено).
Про згоду на запис
Окремо про етику й право. Записувати дзвінки, а тим паче співбесіди, без відома співрозмовника не можна: у багатьох країнах це прямо заборонено законом, а в компаніях зазвичай є власні правила на цей рахунок. Інструмент задуманий для особистого використання: власні диктування, публічні відео, дзвінки, учасники яких знають про запис і згодні на нього. Якщо записуєте інших людей, попередьте їх заздалегідь. На відміну від Teams, інструмент сам нікого не сповіщає про запис, тож відповідальність на тому, хто його запускає.
Галюцинації Whisper
Whisper має відому ваду: на тиші, шумі чи музиці він не мовчить, а вигадує текст. Модель навчали на субтитрах з інтернету, і вилазить саме те, що там часто стояло наприкінці ролика.
- Фрази нізвідки. «Продолжение следует…», «Субтитры сделал DimaTorzok», «Спасибо за просмотр», «Thank you for watching».
- Зациклення. Та сама фраза повторюється кілька разів поспіль.
- Домислювання. На нерозбірливому шматку модель пише правдоподібну фразу, якої ніхто не казав. Це найпідступніше: таке не впадає в око.
Загалом це лікується заміною моделі, і тепер інструмент це вміє (див. наступний розділ). GigaAM і Parakeet влаштовані інакше: вони не вигадують текст вільно, як Whisper, а зіставляють його зі звуком кадр за кадром, тож «Продолжение следует…» їм нізвідки взяти. У моїй перевірці GigaAM на тиші не видав узагалі нічого.
Частину проблеми знімає архітектура. Silero VAD пропускає у Whisper лише ділянки з мовленням, а уривки, коротші за 250 мс, відкидає. Кожну фразу розпізнають окремо, без попереднього тексту, тож одна галюцинація не тягне за собою наступних. Решту вичищає постобробка: LLM легко впізнає «Субтитры сделал…» посеред робочого дзвінка. Для важливих записів варто вмикати --save-audio, щоб спірне місце можна було переслухати за таймстемпом.
Не лише Whisper: GigaAM і Parakeet
Тепер інструмент уміє працювати й з іншими моделями розпізнавання. Прапорець --engine або список Engine у вебінтерфейсі обирає одну з трьох:
| Рушій | Модель | Мови | Коли обирати |
|---|---|---|---|
whisper (за замовчуванням) |
faster-whisper large-v3-turbo |
Будь-які | Для всього; єдиний приймає підказки термінів |
gigaam |
GigaAM v3 від Сбера | Лише російська | Російськомовні дзвінки й диктування |
parakeet |
NVIDIA Parakeet TDT 0.6B v3 | 25 європейських мов, зокрема російська й англійська | Мовлення, де російська змішана з англійською |
GigaAM і Parakeet працюють через onnx-asr на ONNX Runtime, без PyTorch. Під час першого запуску модель завантажується: близько 250 МБ для GigaAM і 700 МБ для Parakeet.
Тут я якраз виміряв. 140 секунд російського мовлення, які той самий детектор мовлення порізав на 7 фраз, MacBook з M3 Max, лише процесор. Для цього в репозиторії є скрипт bench_asr.py, тож вимірювання можна повторити на власних записах.
| Рушій | Час розпізнавання | Пам'ять |
|---|---|---|
| whisper | 90 с | 2,3 ГБ |
| gigaam | 4,4 с | 0,7 ГБ |
| parakeet | 4,8 с | 1,6 ГБ |
GigaAM приблизно в 20 разів швидший за Whisper, а на російській не гірший, подекуди кращий: написав «рулоны бумаги» там, де Whisper почув «роллы на бумаге», і ставить «ё» там, де треба. Parakeet такий самий швидкий, але на російській помітно слабший: губить слова й плутає закінчення. Він має сенс для дзвінків, де російська перемішана з англійською.
Є й мінуси:
- Немає підказок термінів. Whisper можна дати список правильних написань, GigaAM і Parakeet — ні. Натомість прапорець
--vocab-correctзастосовує до готового тексту заміни на кшталт «почуто → правильно» із нотаток проєкту. - GigaAM знає лише російську. Англійське мовлення він перетворює на абракадабру, тож із ним мова завжди російська.
- Довгі шматки. На довгих фрагментах ці моделі працюють гірше, тому монолог без пауз ріжеться на шматки до 20 секунд у найтихішому місці.
Що поки не розв'язано
Одне завдання, яке довго лишалося відкритим, для Whisper вдалося розв'язати: як дати моделі контекст уже на етапі розпізнавання аудіо. Ті самі бренди, імена, терміни — щоб Whisper, скажімо, не розпізнавав Hybris як Hybrid. Тепер правильні написання з нотаток проєкту потрапляють у Whisper як підказки (у консолі — прапорець --vocab-file), і такі помилки зникають уже в сирому транскрипті. GigaAM і Parakeet підказок не приймають, тож для них лишаються заміни після розпізнавання (--vocab-correct) і постобробка за OCR скриншотів.
Головне, що поки не розв'язано, — швидкість обробки. Beautify зараз — окремий і доволі повільний процес: для годинного запису він може тривати хвилин п'ять. А часто хочеться бачити підсумок зустрічі просто на льоту, причому такий, що переписується у міру того, як додається контекст: те, про що говорили на початку, переосмислюється з урахуванням того, що з'ясувалося в середині. Наразі все це працює, але лише після Beautify, тобто з помітною затримкою. Якщо маєте досвід у цьому — радо прийму ідеї та pull request’и.
Друге відкрите питання — вибір кадрів. Pick зараз спирається на те, що й скільки говорили; слайд, на який ніхто не вказав, програє, навіть якщо він найважливіший. Наступні на черзі — OCR кожної сцени й короткий опис від локальної візуальної моделі (Qwen-VL або Moondream через Ollama): тоді слова зі слайда можна буде звіряти з мовленням, а остаточний вибір кадрів віддати текстовій LLM разом із транскриптом.
Де взяти
Інструмент безкоштовний і лежить на GitHub: github.com/raliev/audiotranscripts.
Найпростіший шлях — застосунок для macOS. Це той самий вебінтерфейс у власному вікні, а всередині — Python з усіма залежностями, обидва хелпери й ffmpeg, тож нічого встановлювати не треба. Готові DMG для Apple Silicon і для Intel лежать у релізах на GitHub, потрібна macOS 14+. Моделей усередині немає: кожна завантажується під час першого використання, а прогрес видно на екрані; далі все працює офлайн. Зібрати DMG самостійно можна одним скриптом, packaging/build_app.sh, зокрема одразу з усіма моделями. Під час першого запуску застосунок питає, що використовувати для Beautify і чату: Claude Code чи API з ключем. Підпису Developer ID у мене немає, тому на іншому маку перший запуск — через правий клік → Open.
Для консольної версії потрібні macOS, Python 3.10+ і, за бажанням, ffmpeg для MP3. Система попросить дозволи: Screen Recording для скриншотів, Accessibility для захоплення виділеного тексту і, якщо слухати застосунок напряму, System Audio Recording.
pip install -r requirements.txt
swiftc -O -o screenshot_helper screenshot_helper.swift \
-framework Cocoa -framework Carbon -framework CoreGraphics
# проєкт myproject, автозупинка через 60 хвилин
python main.py myproject 60
# дзвінок удвох, зі звуком у MP3
python main.py teams-sync 60 --speakers 2 --save-audio
# російськомовний дзвінок на GigaAM: набагато швидше й легше за Whisper
python main.py teams-sync 60 --engine gigaam --speakers 2
# дзвінок у навушниках: звук Teams і мікрофон окремо, рядки [Them] і [Me]
./build_audio_helper.sh
./audio_helper --list
python main.py teams-sync 60 --source app+mic:com.microsoft.teams2
# готовий запис замість живого звуку
python main.py lecture 60 --source file:lecture.m4a
# доповідь з автоматичними кадрами (скриншот щосекунди, лишаються тільки нові сцени)
python main.py talk 60 --auto-frames 1
# підібрати пороги кадрів на завантаженому відео, без запису
python autoframes.py talk.mp4 --out /tmp/frames
# постобробка за день
python process_transcript.py myproject 2026-10-01
# або вебінтерфейс на http://127.0.0.1:8765
./webui.shПід час запису працюють хоткеї Ctrl+Shift+S і Ctrl+Shift+W, зупинка — Ctrl+C. Після цього система ще кілька секунд доопрацьовує чергу, тож не закривайте термінал одразу.
Windows і Linux: потрібні волонтери
Наразі інструмент повноцінно працює лише на macOS. До macOS прив'язані тільки хелпери на Swift: глобальні хоткеї через Carbon, знімок екрана через CoreGraphics, виділений текст через імітацію Cmd+C, звук застосунків через Core Audio Process Taps. Усе інше — Python, а faster-whisper, onnx-asr, Silero VAD, EasyOCR, SpeechBrain і sounddevice кросплатформні й мають працювати будь-де.
Фактично портування зводиться до одного: написати хелпер під свою ОС, який говорить тим самим протоколом. Він приймає теку для скриншотів як аргумент і друкує в stdout рядки SCREENSHOT:<шлях> і SELECTION:<текст у base64>. Python-частина майже не змінюється: треба лише обирати бінарник хелпера залежно від платформи.
| Windows | Linux | |
|---|---|---|
| Працює зараз | Ні | Ні |
| Глобальні хоткеї | RegisterHotKey (Win32) або pynput / keyboard на Python |
X11: XGrabKey або pynput; Wayland: глобальні хоткеї застосункам заборонені — через портал GlobalShortcuts або хоткей у налаштуваннях оточення, який викликає команду |
| Скриншот | mss або Windows.Graphics.Capture |
X11: mss; Wayland: портал Screenshot (xdg-desktop-portal), grim на wlroots |
| Виділений текст | Імітація Ctrl+C і читання буфера обміну | X11: PRIMARY selection через xclip / xsel, без копіювання; Wayland: wl-paste --primary |
| Звук із динаміків | WASAPI loopback — можна писати звук напряму, без мікрофона | Monitor-джерело PulseAudio / PipeWire, теж напряму |
| Складність | Середня | X11 — проста, Wayland — помітно складніша |
На маку звук дзвінка тепер захоплюється напряму через Process Taps, а на Windows і Linux для цього є вбудовані механізми з таблиці. Тож режим App + mic переноситься туди так само: потрібен ще один хелпер, який віддає сирий звук у stdout.
Якщо ви готові зробити хелпер під Windows чи Linux — давайте організуємося, ось репозиторій, або пишіть мені. Буду радий.
Користуйтеся на здоров'я.
Discussion
Powered by GitHub Discussions — sign in with a GitHub account to join the conversation.