Як я написав власний B2B-аудіоплеєр на Python, щоб охоронці перестали панікувати під час тривог та блекаутів
Привіт, DOU! Поки індустрія повністю поглинута хмарами, мікросервісами та черговими Electron-додатками, які здатні «вижерти» гігабайт оперативки ще до того, як користувач побачить перше вікно, мені довелося зіткнутися із суворою реальністю українського ритейлу «на землі».
Мене звати Олександр, я працюю системним адміністратором. Останні роки жорстко навчили нас, що стабільність ІТ-інфраструктури в Україні — це поняття дуже й дуже відносне. Сьогодні я хочу поділитися досвідом розробки та розгортання NekoWeave — десктопного додатка для автоматизації фонового звуку та аудіомаркетингу, який я писав 5 місяців під наші, цілком конкретні й жорсткі болі.
Як все починалося: Від простого скрипта до комбайна
- Історія NekoWeave почалася не з бажання зробити музичний плеєр. Спочатку це був просто маленький Python-скрипт під назвою AlertC4tz.py.

Справа в тому, що у нас комп’ютери, які крутять музику в торгових центрах, зазвичай стоять у кімнаті охорони або в операторській. Звук із них виводиться на потужний трансляційний підсилювач. Паралельно на цьому ж компі крутиться специфічний софт для керування вентиляцією та іншими системами будівлі.
Проблема: Коли починалася повітряна тривога, охоронець чи оператор через неуважність (або тому що відійшов) часто не встигав вчасно вимкнути музику і вручну запустити звук сирени та правила евакуації. Людський фактор давав збої. Тому я написав скрипт, який автоматично моніторив тривоги і глушив звук.
Я запустив цей скрипт на п’яти ТЦ в авторежимі. Усе працювало ідеально, аж поки бізнесу не знадобилося «по-нормальному» керувати ще й музикою та рекламою. Класичні плеєри не вміли нормально інтегруватися з моїм скриптом тривог. Саме тоді я подумав: «А чому б не написати повноцінний плеєр із візуальним планувальником і вбудованим моніторингом тривог в одному флаконі?».
На розробку першої стабільної збірки та тести пішло приблизно

Технічний стек та «юридичне пекло»: Чому PySide6, а не PyQt5 чи Electron?
Тягнути Chromium-движок (Electron) на операторський комп’ютер, який і так ледве дихає від софту для вентиляції, було б чистим самогубством. Мені потрібен був монолітний софт, який споживає мінімум ресурсів і працює як годинник у режимі 24/7. Тому я одразу обрав Python як базу.
Але щойно ти переходиш від написання «скриптів для себе» до створення комерційного продукту, ти різко врізаєшся в сувору реальність опенсорс-ліцензування. Я думав: «Зараз швиденько накидаю інтерфейс на улюбленому PyQt5 і готово». Ага, щас. Почавши читати юридичні мануали, я зрозумів, що PyQt5 тягне за собою жорстку вірусну ліцензію GPL.
Якщо ти використовуєш GPL у закритому B2B-продукті, ти зобов’язаний або безкоштовно відкрити всі вихідні коди своєї програми для всіх (що вбиває суть комерційного захисту), або платити величезні відрахування за комерційну ліцензію компанії Riverbank Computing. Це був холодний душ. Мені довелося витратить купу часу, вичитуючи ліцензійні угоди десятків бібліотек, розбираючись, що можна спокійно тягнути в пропрієтарний софт, а за що доведеться відповідати.
Так я прийшов до PySide6 — офіційного порту від самої Qt Company. Він розповсюджується під LGPL v3, що легально дозволяє динамічно лінкувати його в закритий комерційний софт абсолютно безкоштовно, за умови правильного зазначення авторства. У результаті я створив акуратний файл LICENSE_NOTICES.txt, де чесно перерахував усі використані компоненти (PySide6, NumPy, Requests), і мій софт став юридично «чистим».
Щодо аудіо-рушія: стандартний QtMultimedia — це ще той біль. Він періодично ловить краші при обривах мережі. Тому для керування звуком під Windows (особливо для реалізації ефекту Ducking при тривогах) я задіяв бібліотеку PyCAW (Python Core Audio Windows). Вона розповсюджується під безпечною MIT-ліцензією і дозволяє напряму смикати системний мікшер через COM-інтерфейси (WASAPI). Я отримав можливість філігранно керувати гучністю додатка на рівні самої ОС, без милиць.
Біль компіляції, Антикраш та магія Nuitka
Замість класичного PyInstaller я використав Nuitka. Це крутий транслятор, який переганяє Python-код у C++ і компілює його через компілятор MSVC або GCC у справжній бінарний .exe.
На практиці збірка великого PySide6 проєкту через Nuitka виглядає як окремий вид мазохізму, поки не підбереш правильні прапори і не налаштуєш «антикраш» механізми (через специфіку роботи Qt-плагінів у Windows). Щоб софт запускався на будь-якій «чистій» Windows 10/11 без відвалу графічного рушія, моя фінальна команда збірки виглядає так:
nuitka —standalone —enable-plugin=pyside6 —disable-console —windows-icon-from-ico=app_icon.ico —onefile main.py
Прапорець —onefile у зв’язці з —standalone змушує Nuitka не просто зробити зліпок, а розкласти все в оптимізований бінарник. На виході я отримав один файл розміром рівно 169 МБ. Я запакував його через Inno Setup і закинув на GitHub. При старті він не тупить, як це робить PyInstaller. Крім того, декомпілювати збірку Nuitka в рази складніше, що дає базовий захист логіки від реверс-інжинірингу.
Детектор тиші та Автоматичний Failover: Коли провайдер «лягає»
Якщо ви повністю залежите від інтернет-радіо, і десь на магістралі обірвався лінк або блимнуло світло — починається катастрофа.
Щоб вирішити цю проблему, я реалізував у NekoWeave власний Silence Detector. Замість того, щоб чекати абстрактних помилок від сокета, система аналізує реальний уровень звуку (dB) на льоту.
Технічно розрахунок RMS (середньоквадратичного значення) для перевірки наявності звуку в чанку виглядає наступним чином. Ми беремо масив байт із буфера, перетворюємо його за допомогою numpy у масив амплітуд і рахуємо рівень у децибелах:
import numpy as np def calculate_db(audio_data): # Перетворюємо сирі байти буфера у формат float32 samples = np.frombuffer(audio_data, dtype=np.float32) if len(samples) == 0: return —100.0 # Рахуємо Root Mean Square (RMS) rms = np.sqrt(np.mean(samples**2)) if rms == 0: return —100.0 # Переводимо амплітуду в логарифмічні децибели db = 20 * np.log10(rms) return db
Якщо цей метод стабільно повертає значення нижче заданого порогу (наприклад, —50 dB) протягом п’яти секунд, ми фіксуємо аварію стріму. Отримавши подію StreamDeadEvent, плеєр миттєво перемикає вихідний аудіо-канал на локальний «Резервний пул» — папку на диску з MP3-треками. Перемикання йде через crossfade (плавне згасання), тому відвідувачі нічого не підозрюють. Коли онлайн-радіо «оживає», музика так само плавно повертається назад. Поцька автономність досягнута.
Інтеграція з API alerts.in.ua: Автоматизація цивільного захисту
Як я вже казав, це була першочергова фіча. Окремо хочу висловити величезну подяку команді проєкту alerts.in.ua — вони офіційно дозволили мені використовувати їхнє API в моїй програмі. Це дуже крутий приклад взаємопідтримки в нашому ІТ-ком’юніті!
Щоб усе працювало, я зробив максимально дружній інтерфейс. У налаштуваннях NekoWeave є розділ «Тривоги» зі спеціальним полем для вводу API-ключа. Там же розміщено посилання та коротку інструкцію: клікаєш — відкривається сторінка запиту токена, де будь-який користувач може абсолютно безкоштовно отримати свій персональний ключ. Копіюєш, вставляєш у програму — і все! Після цього в головному вікні додатка одразу активується живий моніторинг і навіть візуальна карта тривог.
Оскільки закладу в Одесі не цікаво, що відбувається у Львові, в налаштуваннях є жорстка геолокаційна прив’язка: можна обрати конкретну область, район чи територіальну громаду.
Фоновий потік опитує API кожні
Ducking ефект у дії: Коли статус в API змінюється на active, спрацьовує механізм «Ducking». Гучність основної музики плавно знижується до
Візуальний планувальник та маркетингові ін’єкції
Закладу постійно потрібно крутити акції: «Зачиняємося через 15 хвилин» або правила ТЦ. У звичайних плеєрах це робиться через жахливі плейлисти. Це неможливо зробити, якщо у вас грає безперервний інтернет-стрим.
Я написав свій Візуальний планувальник (Visual Scheduler) за принципом легкої DAW (Digital Audio Workstation).
- Є візуальна сітка на 7 днів тижня.
- Можна «врізати» аудіоролик у точний час (наприклад, 14:15:00).
- І головна кіллер-фіча — матриця пріоритетів. Ролик може поставити фоновий стрим на паузу, може зробити плавний Ducking, а може просто накластися поверх.
Усе це працює асинхронно і не блокує основний цикл обробки подій PySide6 (Main GUI Thread). Інтерфейс не зависає навіть під час рендерингу складних розкладів.
Граблі, на які я наступив: GC, Throttling та DWM
Коли пишеш великий додаток на зв’язці C++ (Qt) та Python, тебе чекає кілька дуже специфічних пасток, які не гугляться за п’ять хвилин. У коді налаштувань NekoWeave я залишив багато коментарів із приставкою STABILITY та FIX, бо крові вони випили чимало. Ось мій топ-3:
1. Збирач сміття (GC) вбиває ваші потоки У Python є класична проблема з Qt: ви створюєте QThread або воркер у локальній області видимості методу, запускаєте його, метод завершується... і Python’івський Garbage Collector радісно видаляє об’єкт з пам’яті, хоча фоновий C++ потік все ще працює. У результаті програма або тихо «ковтає» потік, або вибухає з Segmentation fault. Довелося створювати глобальне сховище для фонових задач (self.activeworkers = []), щоб штучно утримувати посилання на об’єкти, поки вони самі не відрапортують через сигнал finished.
2. DDoS-атака на власний Event Loop Оскільки я додав вбудований DSP (Gate, Limiter, AGC) та керування системним мікшером через WASAPI, виникла проблема з повзунками в GUI. Якщо повісити оновлення параметрів звуку прямо на сигнал valueChanged, то швидкий рух мишкою генерує сотні подій на секунду. Це перевантажувало чергу повідомлень (Event Loop), викликаючи жорсткі лаги звуку та фризи інтерфейсу. Рішення — Throttling (дебаунсинг) через QTimer. Тепер, коли юзер тягне повзунок, застосування параметрів відкладається на кілька десятків мілісекунд, поки рух не зупиниться. Захист від DDoS-атаки на власну ж програму!
3. Кастомний дизайн і Windows DWM Щоб інтерфейс виглядав сучасно (без потворних білих рамок Windows), стандартного прапорця Qt.FramelessWindowHint виявилося замало — залишалися артефакти. Довелося лізти через ctypes напряму у Windows API і відключати рендеринг рамок на рівні самого Desktop Window Manager (через передачу winId() вікна у системні DLL).
Підсумки: Що далі?
Загалом, незважаючи на всі граблі, програма вийшла напрочуд придатною для життя. Вона успішно тримає ефір на точках, не просить їсти і не вимагає ніякого шаманства після початкового налаштування. Музика, реклама і тривоги тепер живуть в єдиному зручному моноліті.
Розробка NekoWeave довела мені одну просту річ: десктопні програми все ще критично важливі для офлайн-бізнесу «на землі». А зв’язка Python + PySide6 + Nuitka — це просто імба, якщо ти хочеш отримати майже нативну швидкість, легально закрити код і не сивіти при роботі зі звуком (дякую, PyCAW).
Звісно, попереду ще купа планів. Є багато цікавих ідей, які хочеться впровадити: треба допиляти більш жорстку аналітику відтворення, погратися з новими DSP-фільтрами і, можливо, навчити систему ще розумніше міксувати треки. Коротше кажучи, беклог пухне, роботи непочатий край, тому нудьгувати точно не доведеться!
Дякую за увагу! Буду радий відповісти на ваші запитання в коментарях. Залюбки обговорюю архітектуру PySide6-додатків, нюанси компіляції через Nuitka та той самий біль роботи з WASAPI під Windows.
6 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівТам ще якусь нову штучку винайшли, щось про інтелект... А! Штучка Інтелект (ШІ)!
І на зміну DPI вона не реагує?
Можете будь ласка пояснити зв’язок між цими двома реченнями? )) /s
Хіба «для всіх»? Здається, тільки для того, кому передаєте програму (але з правом подальшого розповсюдження за тією ж ліцензією)
Сергію, привіт! Сарказм зараховано, цілком справедливо)
З приводу Python і «працює як годинник 24/7» — згоден, на перший погляд звучить як оксюморон. Чому тоді взагалі обрали пітон, а не чисті «плюси»? Все банально — швидкість розробки і гнучкість. Мені треба було дуже швидко ітерувати продукт, постійно допилювати бізнес-логіку, інтегрувати сторонні API для парсингу тривог тощо. На С++ ми б просто закопалися в компіляції та дебагу інтерфейсу ще на старті.Це моя думка.
Тому магія в тому, що пітон ми юзаємо суто як клей. Увесь важкий DSP-процесинг (аудіо-рушій, еквалайзери, компресори, деесери) крутиться на оптимізованих бінарниках під капотом через numpy/scipy, плюс QtMultimedia та WebEngine для рендеру мапи тривог. Тобто сам пітон просто диригує цим C/C++ оркестром. За рахунок такого гібриду ми отримали швидкий time-to-market, але при цьому перфоманс виходить цілком пристойний і пам’ять не тече відрами.
А по GPL — ти абсолютно правий, дякую за виправлення. Моє формулювання у статті дійсно вийшло занадто загальним. Там же фішка в самому тригері дистриб’юції (distribution) — вихідний код ти зобов’язаний надати суто тому клієнту, якому передаєш бінарник, а не всьому світу на старті. Але оскільки GPL прямо забороняє будь-які додаткові обмеження (no further restrictions), ми юридично не маємо права закрити це якимось внутрішнім NDA. Клієнт отримує повне законне право викласти сирці хоч на GitHub під тією ж ліцензією. І для закритого B2B продукту це автоматично перетворюється на «відкрити для всіх». Саме цей ризик я і мав на увазі)
Дрібнуваті букви та ще й якогось не зовсім білого кольору. Багато місця виділено під аналізатор спектру? (нащо він там треба?) та під карту. Це для краси? Ховання менюшок за трьома крапочки вкрай погана мода. Невже немає місця під кнопочки та менюшки? Майже всі написи українською а broadcast active чомусь ні. Мабуть програмою можна повноцінно користуватися лише мишкою і вона не поміщається на екранах з низькою роздільною здатністю. Вагадав? І смужки прокрутки тощиною в три пікселя?
Дякую за такий детальний розбір! Хоча по одному скріншоту важко передати всю ергономіку — краще закинути софт на тест і поклацати вживу, він абсолютно безкоштовний.
Стосовно кольорів і шрифтів — тут є своя логіка. Спеціально уникав стилю а-ля Win 95 або суцільного сірого інтерфейсу. Темні відтінки обрані під реальні умови: сидіти в темній операторській за світлим додатком — це гарантований біль для очей.
Щодо аналізатора спектру... Спалили контору) Так, це більше декоративна штука, щоб трохи оживити екран. Але ти маєш рацію, місця він віджирає забагато. Буду думати, як зробити його ресайзабельним або взагалі сховати. А от мапа — це не просто так, вона зашита під гео-моніторинг тривог по регіонах.
По решті пунктів розклав усе чітко:
Три крапки: хотілося мінімалізму, але згоден, критичні кнопки треба витягувати на видне місце.
«Broadcast active»: прогавлена стрінга в локалізації, мій косяк, виправлю.
Навігація з клавіатури та шорткати: поки що дійсно біль, усе в процесі.
Роздільна здатність і тонкі скролбари: тут вгадав, обмеження є, а3-піксельні смужки підгледів у сучасних трендах) Буду переробляти на більш зручні.
Візуальний стиль ще активно пиляється, додаток пишу сам у вільний час, тому до фіналу тут ще багато чого зміниться. Але така предметна критика — реально на вагу золота. Закинув усе в беклог!