Абрикос 2
Веб-приложение для навигации по записям контроля радиоэфира: поиск нужного момента, выделение фрагментов и экспорт в аудиоформат.
Требования
- Python 3.9+
-
ffmpeg должен быть установлен и доступен в PATH (или путь указан в
config/settings.yaml)- macOS:
brew install ffmpeg - Ubuntu/Debian:
apt install ffmpeg - Windows:
winget install -e --id Gyan.FFmpeg
- macOS:
- прочитать документацию последовательно и полностью.
Установка Python и системных пакетов
Linux (Ubuntu / Debian)
sudo apt update
sudo apt install ffmpeg python3 python3-venv python3-dev
# Для монтирования SMB-источников
sudo apt install cifs-utils
# Для Kerberos / GSSAPI (только при auth_protocol: kerberos в конфиге SMB)
sudo apt install gcc libkrb5-dev
macOS
Рекомендуется установка через Homebrew:
# Установка Homebrew (если ещё не установлен)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Установка Python
brew install ffmpeg python
Альтернатива — официальный установщик с python.org.
Windows
Откройте PowerShell и установите всё через winget:
winget install Gyan.FFmpeg Python.Python.3.12
После установки Python перезапустите терминал, чтобы обновился PATH, затем проверьте:
python --version
Установка пакетов Python в виртуальное окружение
git clone https://github.com/ykmn/apricot2
cd apricot2
[!WARNING] На всех платформах используйте виртуальное окружение — это изолирует пакеты проекта от системного Python и избавляет от ошибки
externally-managed-environment(Ubuntu 23.04+ / Debian 12+).
# 1. Создать окружение (один раз)
python3 -m venv .venv # Linux / macOS
python -m venv .venv # Windows
# 2. Активировать окружение (каждый раз перед работой с проектом)
source .venv/bin/activate # Linux / macOS
.venv\Scripts\activate # Windows (cmd)
.venv\Scripts\Activate.ps1 # Windows (PowerShell)
# 3. Установить зависимости
pip install -r requirements.txt
# Для Kerberos / GSSAPI (только при auth_protocol: kerberos в конфиге SMB)
pip install -r requirements-kerberos.txt
После активации в начале строки терминала появится префикс (.venv). Команду python apricot2.py также нужно выполнять с активированным окружением.
[!CAUTION] Ошибка
externally-managed-environmentозначает, что вы запускаетеpipвне виртуального окружения на современном Linux. Решение — создать и активировать.venvкак показано выше. Обходной флаг--break-system-packagesиспользовать не рекомендуется.
Конфигурация
Структура папок конфигурации
config/ # Рабочая конфигурация (создаётся вами)
├── settings.yaml # Глобальные настройки
├── secret.yaml # Учётные данные SMB
├── ldap.yaml # Авторизация (опционально — если файл отсутствует, авторизация отключена)
├── users.yaml # Локальные пользователи (если Local: true в ldap.yaml)
├── stations/
│ ├── retro_fm.yaml # Одна станция = один файл
│ └── europa_plus.yaml
└── playlogs/
├── retro_fm.yaml # Один плейлог для станции = один файл
└── europa_plus.yaml
config.demo/ # Демо-конфигурация
Скопируйте config.demo/ → config/ как отправную точку:
cp -r config.demo/ config/
Учётные данные SMB (config/secret.yaml)
Все логины, пароли и домены хранятся в одном файле config/secret.yaml, на который ссылаются конфиги станций по числовому id. Файл добавлен в .gitignore и никогда не попадает в репозиторий. Создавайте его сами по образцу:
# config/secret.yaml
authorization:
- id: 1
username: "logger"
password: "password1"
domain: "CORP"
- id: 2
username: "svc_radio"
password: "another_pass"
domain: "CONTOSO"
- id: 3
username: "user1"
password: "..."
domain: "EVILCORP"
auth_protocol: "kerberos" # кросс-доменный доступ: использует тикет OS
Поле auth_protocol (опционально):
| Значение | Когда использовать |
|---|---|
не задано / ntlm
|
Сервер в том же домене или доступен по NTLM |
kerberos |
Сервер в другом домене/лесу с трастом; машина включена в домен — Kerberos использует текущий тикет Windows, явные credentials игнорируются |
[!TIP] Альтернатива для отдельных каналов —
password_env: "MY_ENV_VAR"(хранение пароля в переменной окружения).
[!IMPORTANT] Защита файла от посторонних.
secret.yamlсодержит пароли в открытом виде — ограничьте права доступа к нему. На Linux/macOS выполните один раз после создания файла:chmod 600 config/secret.yamlЭто разрешает чтение и запись только владельцу файла. Убедитесь, что приложение запускается от того же пользователя. Файл уже добавлен в
.gitignoreи не попадёт в репозиторий, но права на файловой системе нужно выставить вручную.
Конфигурация станций
В файле конфигурации станции для одной радиостанции может быть несколько каналов записи из разных источников: PGM, FM, SAT и т.д.; используйте YAML-массив channels:
Подключение через SMB (network share)
Используйте secret: N, где N — id из config/secret.yaml:
# config/stations/retro_fm.yaml
id: retrofm
name: "Retro FM"
channels:
- id: retrofm_pgm
name: "Retro FM — PGM (mp3/64k)"
smb:
host: "fileserver.domain.local" # имя или IP сервера
share: "LOGGER" # имя сетевой шары
path: "04 Retro/PGM1" # подпапка внутри шары (необязательно)
secret: 1 # ссылка на запись в secret.yaml
folder_format: "%Y-%m-%d"
file_format: "%H-%M-%S"
file_extension: "mp3"
sample_rate: 48000
playlogs:
- retrofm
playlogs_offset: 500 # смещение плейлога относительно аудио в мс (положительное — вперёд, отрицательное — назад; по умолчанию 0)
- id: retrofm_fm
name: "Retro FM — FM (mp3/64k)"
smb:
host: "fileserver.domain.local"
share: "LOGGER"
path: "04 Retro/FM"
secret: 1
folder_format: "%Y-%m-%d"
file_format: "%H-%M-%S"
file_extension: "mp3"
sample_rate: 48000
playlogs:
- retrofm
playlogs_offset: 750
Путь на сервере формируется как: \\host\share\path\<папка-даты>\<файл-время>.ext.
playlogs_offset — смещение меток плейлога относительно аудиозаписи в миллисекундах. Плейлог фиксирует момент постановки трека в эфир, тогда как в записи он слышится чуть позже (задержки кодирования, буферизации, сигнальной цепи). Положительное значение сдвигает метки вперёд (плейлог отстаёт от звука), отрицательное — назад. По умолчанию — 0.
Можно также указать учётные данные прямо в конфиге канала (не рекомендуется):
smb:
host: "fileserver"
share: "LOGGER"
path: "04 Retro/PGM1"
username: "logger"
password: "passw"
# или password_env: "MY_VAR"
domain: "CONTOSO"
Локально смонтированная папка
Если SMB-шара уже смонтирована как буква диска (Windows) или точка монтирования (Linux/Mac):
channels:
- id: retro_fm_pgm_hq
name: "Retro FM — PGM-HQ (wav/48000)"
local_path: "/mnt/retro_fm/PGM-HQ" # Linux/Mac
# local_path: "Z:\\PGM-HQ" # Windows (буква диска и двойные слеши в пути к папке)
folder_format: "%Y-%m-%d"
file_format: "%H-%M-%S"
file_extension: "wav"
sample_rate: 48000
playlogs:
- retrofm
Формат имён файлов
Используется синтаксис strftime:
| Параметр | Пример значения | Результат |
|---|---|---|
folder_format: "%Y-%m-%d" |
папки | 2026-06-03 |
file_format: "%H-%M-%S" |
файлы | 18-24-06.mp3 |
file_format: "%d%m%y_%H%M" |
файлы | 030626_1824.wav |
Битрейт (важно для MP3)
Если MP3-файлы записаны в JointStereo, укажите битрейт явно. Иначе длительность файлов будет рассчитана неверно и на таймлайне будут пропуски.
file_extension: "mp3"
bitrate: "64k" # 32k, 64k, 128k, 192k, 320k
При первом сканировании приложение старается определить битрейт автоматически через ffprobe и сохраняет результаты в config/detected_params.yaml. Используйте эти результаты для прафильного конфигурирования.
Плейлог
Каждый файл конфигурации описывает один плейлог Дигиспот Джин Вещание, обычно находящийся в папке C:\Program Files (x86)\DJin\PlayLog. Однако у каждого плейлога может быть несколько источников с разным приоритетом: основной и резервный эфирный компьютер в горячем резерве. Используйте YAML-массив sources:
Стратегия слияния источников:
- Данные приоритета 1 служат основой.
- Если в данных приоритета 1 обнаружен пробел (> 60 секунд без записей) — он заполняется записями из приоритета 2, настроенного на плейлоги с компьютера горячего резерва. Убедитесь, что в папку с плейлогом пишет только резервный эфирный Джин.
- Дубли (одно и то же событие с расхождением ≤ 5 сек по часам двух машин) автоматически исключаются.
Загрузка при старте: при запуске приложение читает и кэширует плейлоги за последние 14 дней (PRELOAD_DAYS). Сегодняшний плейлог автоматически обновляется каждые 30 минут (настраивается через playlogs.today_refresh_interval в settings.yaml).
# config/playlogs/retro_fm.yaml
id: retrofm # это название указывайте в config/stations/retro_fm.yaml
name: "Retro FM — Плейлог"
sources:
- priority: 1
local_path: "/mnt/air-1/Digispot II/DJin/PLAYLOG" # основная эфирная машина с подключенного диска
file_mask: "%Y-%m-%d.log"
encoding: "windows-1251"
delimiter: ","
header_skip_prefix: "FIELD LIST" # первый столбец строки-заголовка (пропустить)
- priority: 2
smb: # резервная машина через SMB
host: "air-2.domain.local"
share: "Digispot II" # имя сетевой шары
path: "DJin/PLAYLOG" # подпапка внутри шары (пусто = корень)
secret: 1 # логин-пароль для входа в шару из списка секретов
file_mask: "%Y-%m-%d.log"
encoding: "windows-1251"
delimiter: ","
header_skip_prefix: "FIELD LIST"
# Маппинг полей файла во внутренние поля приложения
fields:
datetime: "EventTime" # объединённое поле даты и времени
title: "ElemName" # название трека / элемента
artist: "ElemArtist" # исполнитель (если пусто — только ElemName)
cls: "ElemClass" # класс: M/J/C/P/N
db_id: "ElemDbId" # ID в базе данных → всплывает при наведении
id_number: "ElemIdNumber" # порядковый номер → всплывает при наведении
duration: "ElemLength" # длина элемента в формате MM:SS.mmm (опционально)
class_colors:
M: "#1565c0"
J: "#558b2f"
C: "#e65100"
P: "#6a1b9a"
N: "#37474f"
default: "#424242"
class_names:
M: "Музыка"
J: "Джингл"
C: "Реклама"
P: "Передача"
N: "Новости"
Формат строки плейлога в UI:
Исполнитель — Название (или просто Название, если исполнитель не заполнен).
При наведении на строку показывается всплывающая подсказка: [dbID: 70501 // ID_Number: R071-01].
Кэш плейлогов хранится в cache_playlogs/{playlist_id}/{YYYY-MM-DD}.json.
Данные прошлых дат кэшируются в файлы; сегодняшний день хранится в RAM-кэше с TTL 30 минут и при перезапуске перечитывается заново.
Авторизация
Авторизация по умолчанию отключена. Чтобы включить её, создайте файл config/ldap.yaml. Если файл отсутствует, приложение открывается без запроса входа.
[!WARNING] Если файл
config/ldap.yamlсоздан, ноconfig/users.yamlотсутствует, при каждом запуске приложение генерирует случайный одноразовый пароль для локального пользователяadminи выводит его в stderr:[auth] WARNING: config/users.yaml not found. Temporary admin credentials — login: admin password: xK9mQz… [auth] Create config/users.yaml to set permanent credentials.Создайте
config/users.yamlс постоянными учётными данными до публичного развёртывания.
Локальные пользователи (Local: true)
config/ldap.yaml:
Local: true # включить локальную авторизацию
LDAP: false # доменная авторизация отключена
config/users.yaml:
users:
- username: admin
password: yourStrongPassword # открытый текст (не рекомендуется)
password_pbkdf2: "$pbkdf2$sha256$600000$<salt>$<hash>". # хэш пароля PBKDF2-SHA256
is_admin: true # доступ к функциям управления в меню
- username: user
password_pbkdf2: "$pbkdf2$sha256$600000$<salt>$<hash>". # хэш пароля PBKDF2-SHA256
is_admin: false # только навигация и экспорт
[!TIP] Хранение паролей в виде хэша (рекомендуется). Вместо
password:используйтеpassword_pbkdf2:— пароль хранится в виде необратимого хэша (PBKDF2-SHA256). Генерируйте хэш утилитой:python tools/hash_password.pyСкрипт запросит пароль дважды и выведет готовый YAML-сниппет. Пример результата:
users: - username: admin password_pbkdf2: "$pbkdf2$sha256$600000$<salt>$<hash>" is_admin: trueПриоритет полей:
password_pbkdf2→password(открытый текст).
Доменная авторизация Windows AD (LDAP: true)
Требует установки пакетов ldap3 и pycryptodome:
pip install ldap3 pycryptodome
[!IMPORTANT] Пакет
pycryptodomeнужен для работы NTLM-хэширования на Python 3.x + OpenSSL 3.x (т.к. OpenSSL 3 убрал алгоритм MD4, который использует NTLM). Пакет уже включён вrequirements.txt.
config/ldap.yaml:
LDAP: true
server: "ldap://dc01.corp.local" # URL LDAP-сервера
domain: "CORP" # NetBIOS-имя домена (для NTLM-привязки)
base_dn: "DC=corp,DC=local" # Корень поиска в каталоге
# Сервисный аккаунт AD для поиска пользователей (read-only).
# Ссылается на запись в secret.yaml по полю id.
# Рекомендуется: без него диагностика ошибок менее точная.
bind_secret: 10
# Группы с правами администратора (полный доступ + меню управления).
# Любой авторизованный доменный пользователь получает доступ на просмотр автоматически.
admin_groups:
- "CN=RadioAdmins,OU=Groups,DC=corp,DC=local"
Сервисный аккаунт (config/secret.yaml, запись с id: 10):
authorization:
- id: 10
username: "ldap" # read-only аккаунт в AD
password: "ldap-view-password"
domain: "CORP"
[!NOTE] Права доступа: любой пользователь, успешно прошедший аутентификацию в одном из настроенных доменов, получает доступ на просмотр. Если пользователь входит в группу из
admin_groups— получает права администратора.
Формат имени пользователя на странице входа:
Поддерживаются три формата:
| Формат | Поведение |
|---|---|
a.pushkin |
Поиск последовательно во всех настроенных доменах |
CORP\a.pushkin |
Поиск только в домене с name: CORP в ldap.yaml |
a.pushkin@corp.local |
Поиск только в домене с base_dn: DC=corp,DC=local (или upn_suffix: corp.local) |
Поддержка нескольких доменов:
Параметр domains: в ldap.yaml позволяет настроить несколько доменов. Попытка аутентификации идёт последовательно:
- Если пользователь не найден в домене, переходим к следующему
- Если сервер недоступен, переходим к следующему
- Если пароль неверный или нет прав доступа — сразу возвращается ошибка
Несколько доменов (кросс-доменные трасты или несколько лесов):
LDAP: true
domains:
- name: "CORP"
server: "ldap://dc01.corp.local"
base_dn: "DC=corp,DC=local"
bind_secret: 10 # id сервисного аккаунта из secret.yaml
- name: "PARTNER"
server: "ldap://dc01.partner.local"
base_dn: "DC=partner,DC=local"
bind_secret: 11
# Администраторы — применяется ко всем доменам (можно переопределить внутри каждого домена)
admin_groups:
- "CN=RadioAdmins,OU=Groups,DC=corp,DC=local"
При входе без указания домена (a.pushkin) домены перебираются по порядку. При ошибке «пользователь не найден» или «сервер недоступен» попытка продолжается в следующем домене. Если пароль неверный — перебор останавливается.
Global Catalog (один лес AD, несколько доменов):
Для поиска по всем доменам леса достаточно одной записи с Global Catalog на порту 3268:
LDAP: true
server: "ldap://gc.corp.local:3268"
base_dn: "DC=corp,DC=local"
bind_secret: 10
admin_groups:
- "CN=RadioAdmins,OU=Groups,DC=corp,DC=local"
Порядок проверки при входе:
- Если пользователь найден в
users.yamlпроисходит локальная проверка пароля (AD не опрашивается) - Если в локальном списке нет происходит запрос к Active Directory (один домен или список)
Комбинированный режим
Можно включить оба метода одновременно. Порядок проверки фиксированный: сначала локальные пользователи, затем AD.
LDAP: true
Local: true
# ...остальные параметры LDAP...
Если имя пользователя есть в users.yaml — AD не опрашивается вообще. Если нет, запрос идёт в AD.
Права администратора
Пользователи с is_admin: true (локальные) или входящие в admin_groups (доменные) видят в меню ☰ дополнительные пункты управления:
- Пересканировать текущий канал
- Пересканировать плейлоги станции
- Обновить конфигурацию без перезапуска
- Перезапустить сервер
Сессии
После успешного входа в браузере сохраняется cookie-сессия. Продолжительность сессии задаётся параметром server.session_ttl в config/settings.yaml (в секундах, по умолчанию 604800 = 7 дней). Сессии хранятся в config/sessions.yaml и при перезапуске восстанавливаются с проверкой на истечение по времени.
Глобальные настройки (config/settings.yaml)
server:
host: "0.0.0.0"
port: 8765
session_ttl: 604800 # продолжительность cookie-сессии в секундах (604800 = 7 дней)
log_screen: "INFO" # минимальный уровень логов в консоль (DEBUG/INFO/WARNING/ERROR)
log_file: "INFO" # минимальный уровень логов в файл logs/YYYY-mm-dd.log
ssl:
enabled: false # true — запустить сервер по HTTPS
cert: "ssl/cert.crt" # путь к сертификату (относительно корня проекта)
key: "ssl/server.key" # путь к приватному ключу
watcher:
poll_interval: 10 # интервал опроса SMB-шар в секундах
indexing:
rescan_all_on_startup: false # true — полное пересканирование всех аудиофайлов
# (без дискового кеша, с передетектом битрейта)
# при каждом запуске/перезапуске сервера
playlogs:
today_refresh_interval: 1800 # обновление сегодняшнего плейлога (секунды, по умолчанию 30 мин)
export:
default_format: "mp3"
default_bitrate: "192k"
retention_days: 3 # экспортированные файлы удаляются через N дней
cleanup_time: "03:00" # время ежедневной очистки папки export/ (HH:MM)
ffmpeg_path: "ffmpeg" # путь к ffmpeg (или просто "ffmpeg" если он есть в PATH)
ui:
timezone: "Europe/Moscow"
Папка export/ очищается автоматически: при старте приложения и ежедневно в cleanup_time.
HTTPS
Поместите файлы сертификата и ключа в папку ssl/ и включите HTTPS в config/settings.yaml:
server:
ssl:
enabled: true
cert: "ssl/cert.crt" # путь к сертификату
key: "ssl/server.key" # путь к приватному ключу
Файлы ssl/*.pem, ssl/*.key, ssl/*.crt добавлены в .gitignore и не попадают в репозиторий.
Самоподписанный сертификат (для тестирования):
openssl req -x509 -newkey rsa:4096 -keyout ssl/server.key -out ssl/cert.crt \
-days 365 -nodes -subj "/CN=localhost"
Сертификат от Let's Encrypt (certbot):
certbot certonly --standalone -d your.domain.com
# Затем скопируйте или сделайте symlink:
cp /etc/letsencrypt/live/your.domain.com/fullchain.pem ssl/cert.crt
cp /etc/letsencrypt/live/your.domain.com/privkey.pem ssl/server.key
При запуске сервер автоматически переключается на https:// и проверяет наличие файлов до старта.
[!WARNING] Порт 443 на Linux/macOS требует прав root. Если запускаете от обычного пользователя, есть три варианта:
Вариант 1 — authbind (рекомендуется):
sudo apt install authbind sudo touch /etc/authbind/byport/443 sudo chmod 500 /etc/authbind/byport/443 sudo chown logger /etc/authbind/byport/443 authbind --deep /home/logger/apricot2/.venv/bin/python3 apricot2.pyЗамените
loggerна своё имя пользователя и путь к venv при необходимости.Вариант 2 — setcap:
sudo setcap 'cap_net_bind_service=+ep' $(readlink -f /home/logger/apricot2/venv/bin/python3)Вариант 3 — использовать порт
8443вconfig/settings.yaml.
Автоматическое монтирование SMB (Linux / macOS)
На Linux и macOS при запуске приложение автоматически монтирует все SMB-источники, настроенные в конфигурации станций и плейлогов. Каждая уникальная шара монтируется в папку mounts/<host>/<share>/ внутри директории проекта.
Повторный запуск или перезапуск безопасен: перед монтированием проверяется, подключена ли уже шара, и дубликаты не создаются.
Если монтирование завершается с ошибкой (нет прав, хост недоступен и т.п.), приложение выводит предупреждение и продолжает работу, используя smbprotocol для доступа к SMB напрямую.
Права для монтирования
Linux вызывает mount.cifs напрямую через sudo (без /bin/mount). Добавьте в /etc/sudoers через visudo:
logger ALL=(root) NOPASSWD: /usr/sbin/mount.cifs
Путь к бинарнику уточните командой which mount.cifs — на некоторых системах это /usr/sbin/mount.cifs.
[!TIP]+ Можно запустить приложение от
root(например, в Docker с--privileged): в этом случаеsudoне нужен.
macOS использует mount_smbfs, который доступен без прав администратора для текущего пользователя.
Ручное размонтирование
# Linux
sudo umount mounts/fileserver.domain.local/LOGGER
# macOS
umount mounts/fileserver.domain.local/LOGGER
Запуск вручную
# Активируйте окружение
source .venv/bin/activate # Linux / macOS
.venv\Scripts\activate # Windows (cmd)
.venv\Scripts\Activate.ps1 # Windows (PowerShell)
python apricot2.py
# Деактивация виртуального окружение
deactivate
Откройте браузер: http://localhost:8765 (или https://localhost:8765 при включённом SSL)
Запуск как системный сервис (Linux, systemd)
Позволяет запускать приложение автоматически при старте системы и перезапускать при сбоях.
1. Создайте файл сервиса
Предполагается, что проект находится в домашней папке пользователя ~/apricot2. Замените logger на своё имя пользователя.
sudo nano /etc/systemd/system/apricot2.service
[Unit]
Description=Абрикос 2 — веб-интерфейс контроля радиоэфира
After=network.target
[Service]
Type=simple
User=logger
WorkingDirectory=/home/logger/apricot2
ExecStart=/home/logger/apricot2/.venv/bin/python3 apricot2.py
Restart=always
RestartSec=2
# Логи доступны через: journalctl -u apricot2
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
[!NOTE]+
ExecStartуказывает напрямую на Python внутри.venv— активировать окружение вручную не нужно.
[!IMPORTANT]+
Restart=always— обязательно при использовании кнопки «Перезапустить сервер» в меню ☰. Приложение завершается с кодом 0, аRestart=on-failureкод 0 не считает сбоем и процесс не перезапускает.
[!IMPORTANT]+ Если приложение слушает порт 443, замените в секции
[Service]:ExecStart=/usr/bin/authbind --deep /home/logger/apricot2/.venv/bin/python3 apricot2.pyПредварительно настройте authbind (см. раздел HTTPS).
2. Включите и запустите сервис
# Перечитать конфигурацию systemd
sudo systemctl daemon-reload
# Включить автозапуск при старте системы
sudo systemctl enable apricot2
# Запустить прямо сейчас
sudo systemctl start apricot2
3. Управление сервисом
sudo systemctl status apricot2 # статус и последние строки лога
sudo systemctl stop apricot2 # остановить
sudo systemctl restart apricot2 # перезапустить
sudo systemctl disable apricot2 # отключить автозапуск
4. Просмотр логов
# В реальном времени
journalctl -u apricot2 -f
# Последние 100 строк
journalctl -u apricot2 -n 100
# За сегодня
journalctl -u apricot2 --since today
5. Права для монтирования SMB из сервиса
Если приложение запускается не от root, для авто-монтирования SMB нужно разрешить пользователю использовать mount.cifs без пароля. Добавьте в /etc/sudoers через visudo:
logger ALL=(root) NOPASSWD: /sbin/mount.cifs
Либо смонтируйте SMB-шары через /etc/fstab с опцией _netdev — тогда они будут доступны до старта сервиса и авто-монтирование в apricot2.py просто подтвердит, что шара уже подключена.
6. Обновление в Linux
cd ~/apricot2 && git pull && sudo systemctl restart apricot2.service
Использование веб-приложения
Выбор канала
В строке управления под заголовком:
- Поле Фильтр… — введите часть названия для быстрого поиска
- Кнопка ▾ — открыть полный список каналов (сгруппирован по станциям)
- Кнопка ⧉ — скопировать путь к папке канала в буфер обмена (
\\server\share\pathдля SMB, локальный путь для смонтированных шар и локальных папок) - Кнопка # — скопировать ссылку на текущее состояние. Ссылка содержит идентификатор канала и позицию таймлайна (
?ch=…&t=…) - Двойной клик на названии канала — скопировать название в буфер обмена
Навигация по таймлайну
| Ряд | Масштаб | Прокрутка |
|---|---|---|
| 1 | Секунды | Посекундно |
| 2 | Минуты | Поминутно |
| 3 | Часы | Почасово |
| 4 | Дни | Подневно |
| 5 | Месяцы | Помесячно |
- Перетащите любой ряд влево/вправо для перемотки
- Колёсико мыши — прокрутка на нужном ряду
- ⟳ — прыжок к текущему времени
- Горячие клавиши:
-
S— фокус на поле выбора канала; -
[/]— метка In/Out; -
0— Play/Pause; -
+/-— шаг по строке таймлайна
-
Цветовая индикация
- Синие ячейки — запись есть
- Тёмные ячейки — запись отсутствует
- Жёлтая подсветка — выделенный фрагмент
Выделение фрагмента
- Перейдите к началу нужного момента
- Нажмите In (установить начало)
- Перемотайте к концу фрагмента
- Нажмите Out (установить конец)
- Нажмите Добавить фрагмент
Экспорт
- Нажмите ↓ рядом с записью в списке фрагментов
- В диалоге экспорта:
- По умолчанию выбирается оригинальный формат канала — экспорт выполняется максимально быстро без перекодирования и без потерь качества
- При выборе другого формата — выполняется перекодирование с указанными параметрами
-
Скачать всё — открывает диалог с выбором:
- Скачать отдельными файлами — экспортирует и скачивает все фрагменты из списка последовательно отдельными файлами.
-
Склеить фрагменты в один файл — объединяет все выбранные фрагменты в один аудиофайл; порядок склейки на выбор — либо по времени начала фрагмента, либо по очереди в списке. Внутри файла расставляются маркеры с датой-временем начала каждого исходного фрагмента:
- MP3 — главы (ID3v2 CHAP/CTOC, читаются большинством плееров) и маркеры в собственном формате Sound Forge (
GEOB:SfMarkers), которые не показывают обычные плееры, но открываются в Sound Forge/аналогичных редакторах — записываются оба варианта одновременно - WAV — маркеры (RIFF cue-points), читаются DAW/аудиоредакторами (Sound Forge, Audition и т.п.)
- AAC — маркеры технически невозможны (сырой поток без контейнера), файл склеивается без меток
- MP3 — главы (ID3v2 CHAP/CTOC, читаются большинством плееров) и маркеры в собственном формате Sound Forge (
Имя экспортируемого файла: {Название канала} {YYYY-mm-dd} {HH-MM-SS}.{fmt}
Пример: Europa Plus PGM 2026-06-03 14-30-00.mp3
Имя склеенного файла: {Название канала|Несколько каналов} склейка с {YYYY-mm-dd} {HH-MM-SS}.{fmt}
Недопустимые символы в названии канала заменяются пробелом.
Экспортированные файлы хранятся на сервере в папке export/ и автоматически удаляются через retention_days дней.
Список выбранных фрагментов
Список фрагментов сохраняется в localStorage браузера и не сбрасывается при обновлении страницы. При следующем открытии браузера все ранее добавленные фрагменты восстанавливаются автоматически.
Удаление фрагмента из списка (✕) не сбрасывает текущее выделение на таймлайне.
Воспроизведение
-
▶ — воспроизведение с текущей позиции -
⏸ — пауза - ×1.5 — переключатель ускоренного воспроизведения (в 1,5 раза быстрее); режим остаётся включённым между запусками плеера, пока не выключен повторным нажатием
- Во время воспроизведения таймлайн скроллится через центральную линию в реальном времени
- Перемотка во время воспроизведения — прокрутите таймлайн колёсиком/перетащите или измените дату/время: анимация остановится на новой позиции, через 250 мс начинается буферизация и аудио перескакивает на выбранный момент
- Во время буферизации в строке статуса появляется мигающий индикатор Буферизация…, который гаснет в момент реального начала звука. Для SMB-каналов воспроизведение начинается почти мгновенно: сервер открывает файл как поток и сразу передаёт нужный фрагмент в ffmpeg — без скачивания файла целиком.
- Если канал записывается фрагментами с перерывами (запись только при наличии сигнала), пропуски между фрагментами внутри выбранного диапазона озвучиваются тишиной соответствующей длительности — это удерживает положение плейхеда на таймлайне синхронным с реально звучащим содержимым.
-
Восстановление состояния — при повторном открытии страницы автоматически восстанавливаются выбранный канал, позиция таймлайна, маркеры выделения и список фрагментов. Состояние сохраняется на сервере в
config/sessions-ui.yamlи привязано к имени пользователя, поэтому работает в любом браузере и на любом устройстве.
Плейлог
Левая панель показывает плейлог для выбранного канала в окне ±3 часа от текущей позиции:
- Каждая строка окрашена цветом класса элемента (настраивается в конфиге плейлога)
- Текущий элемент (соответствующий позиции таймлайна) выделен жирным
- При наведении на строку всплывает подсказка с ID из базы данных:
[dbID: ... // ID_Number: ...] - Кнопка ↑ — выделить элемент плейлога на таймлайне (установить метки In/Out). Длина фрагмента берётся из поля
ElemLengthплейлога; если оно не заполнено — вычисляется как расстояние до следующей записи, иначе используется заглушка 3 минуты. Shift+клик расширяет текущее выделение, включая выбранный элемент. Смещение меток относительно реального положения в аудио регулируется параметромplaylogs_offsetв конфиге канала.
Строка статуса индекса
Под транспортными кнопками находится строка аудио-индекса с цветными точками, по одной на канал:
- 🟢 Зелёная — канал просканирован, файлы найдены. В подсказке количество файлов.
- 🟡 Жёлтая — канал доступен, но файлы не найдены (возможно, неверный
file_formatили папка пуста). Нажмите для диагностики. -
🔴 Красная — канал недоступен (ошибка подключения или авторизации). Нажмите на точку — причина ошибки. - ⟳ Синяя мигающая — канал сканируется прямо сейчас.
Клик по тексту «
Точки автоматически пересобираются при «Обновить конфигурацию без перезапуска» — удалённый из конфига канал сразу исчезает из строки, добавленный сразу появляется, без обновления страницы. Ряд точек переносится на новую строку по фактически доступной ширине контейнера.
Строка статуса плейлогов
Ниже строки аудио-индекса — строка доступности источников плейлогов, тоже по одной точке на источник:
- 🟢 Зелёная — папка источника доступна,
.log-файлы найдены и успешно парсятся -
🔴 Красная — папка недоступна, файлов нет или ни один не удалось разобрать. Нажмите — причина ошибки. - ⟳ Синяя мигающая — источник проверяется прямо сейчас.
Источники проверяются по одному, как и аудио-каналы — точки заполняются постепенно, а не все разом, и сохраняют исходный порядок (уже проверенные не перескакивают в конец строки). Проверка запускается автоматически после загрузки плейлогов при старте и по нажатию «Пересканировать плейлоги станции». Ряд точек переносится по ширине контейнера, как и в строке статуса индекса выше.
Индекс файлов и кэш
При запуске приложение:
- Загружает индекс из
cache/*.json(мгновенно) — таймлайн сразу показывает данные - В фоне сканирует SMB-шары и обновляет индекс —
scandir()читает размер файлов из ответа SMB-сервера без отдельныхstat-запросов на каждый файл - Сохраняет обновлённый индекс обратно в кэш
WebSocket-обновления
Сервер следит за появлением/удалением файлов (polling каждые N секунд, настраивается через watcher.poll_interval в settings.yaml). При изменении браузер получает WS-сообщение и мгновенно обновляет цветовую индикацию таймлайна без перезагрузки страницы.
Меню (☰)
| Пункт | Действие | Доступ |
|---|---|---|
|
|
Переключение темы интерфейса | Все |
|
|
Переключение языка интерфейса (русский, английский, французский) | Все |
|
|
Принудительно перечитать список аудиофайлов | Администраторы |
|
|
Очистить кэш плейлогов за последние 2 дня и перепроверить все источники плейлогов текущей станции | Администраторы |
|
|
Перечитать YAML-файлы без перезапуска процесса на сервере | Администраторы |
|
|
Отобразить окно текущих активных сессий пользователей с возможностью принудительно завершить сессию | Администраторы |
|
|
Проверить наличие новых коммитов на update:repo_url из settings.yaml, обновить и автоматически перезапустить приложение |
Администраторы |
|
|
Полный перезапуск процесса; страница обновится автоматически | Администраторы |
|
|
Внизу меню имя текущего пользователя и кнопка «Выйти» в одной строке; значок — |
Все (только при включённой авторизации) |
Архитектура
apricot2/
├── app/
│ ├── main.py # FastAPI-приложение, REST API, WebSocket, auth-middleware
│ ├── auth.py # Авторизация: сессии, локальные пользователи, LDAP/AD
│ ├── config.py # Загрузка YAML-конфигов, разрешение secret.yaml
│ ├── smb_client.py # Единый слой доступа: локальный путь или SMB
│ ├── smb_mount.py # Авто-монтирование SMB при запуске (Linux / macOS)
│ ├── file_index.py # Индекс аудиофайлов в памяти, polling, кэш
│ ├── audio.py # Стриминг и экспорт через ffmpeg (copy / transcode)
│ ├── audio_probe.py # Автоопределение параметров через ffprobe
│ ├── cache.py # Дисковый кэш аудио-индекса (cache_audio/*.json)
│ ├── playlist.py # Чтение CSV-плейлогов
│ └── models.py # Dataclass-модели
├── static/
│ ├── index.html # Главная страница приложения
│ ├── login.html # Страница входа (отдаётся при включённой авторизации)
│ ├── style.css
│ ├── timeline.js # Многоуровневый таймлайн (canvas)
│ └── app.js # Главная логика UI
├── config/ # Рабочая конфигурация (не в репозитории)
│ ├── settings.yaml # Общие настройки сервера
│ ├── secret.yaml # Логины и пароли для SMB
│ ├── ldap.yaml # Авторизация (опционально; при отсутствии — авторизация отключена)
│ ├── users.yaml # Локальные пользователи (опционально)
│ ├── detected_params.yaml # Найденные параметры аудиофайлов (автогенерируется)
│ ├── stations/ # Конфигурации станций и каналов записи
│ │ ├── station1.yaml
│ │ └── station2.yaml
│ └── playlogs/ # Конфигурации плейлогов
│ ├── station1.yaml
│ └── station2.yaml
├── config.demo/ # Демо-конфигурация (пример для копирования в config/)
├── export/ # Экспортированные файлы
├── mounts/ # SMB-шары, смонтированные при запуске (Linux / macOS)
│ └── <host>/<share>/ # Каждая шара — отдельная папка
├── cache_audio/ # Кэш индекса аудиофайлов (автогенерируется)
├── cache_playlogs/ # Кэш плейлогов по датам (автогенерируется)
├── requirements.txt # Зависимости Python
└── apricot2.py # Точка запуска
Disclaimer
[!INFO] Приложение разработано заново по отдельному техническому заданию и, кроме элементов технического задания, не имеет общего кода с проектом "Абрикос" разработки Микро-ИТ.