Le balayage ne servait qu'à trouver un morceau guetté, puis oubliait tout. Il note désormais chaque titre annoncé par les stations, même sans guet en cours : un onglet Écoutes montre l'historique, et la rotation — passages et stations distinctes, par titre et par artiste — sur les 100 dernières remontées. Pour que l'historique soit fiable, un tour va maintenant jusqu'au bout après une trouvaille (l'enregistrement part aussitôt, le début du morceau n'est pas perdu), et la liste des stations est mise en cache une demi-heure au lieu d'interroger l'annuaire à chaque tour. Essayé sur de vraies radios : une station alternait le vrai titre et un titre de repli, ce qui doublait les passages. Un titre déjà relevé sur la même station dans les dix minutes est donc ignoré. Première moitié de la reconnaissance des morceaux ; l'écoute continue et l'empreinte sonore suivront. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
88 lines
3.4 KiB
Python
88 lines
3.4 KiB
Python
# Noxely — collecte côté serveur, écoute hors connexion côté mobile.
|
|
# Copyright © 2026 Johnny (tuxgyver)
|
|
#
|
|
# Ce programme est un logiciel libre : vous pouvez le redistribuer et le modifier
|
|
# selon les termes de la GNU General Public License, version 3 ou ultérieure,
|
|
# telle que publiée par la Free Software Foundation.
|
|
#
|
|
# Il est distribué dans l'espoir d'être utile, mais SANS AUCUNE GARANTIE.
|
|
# Voir le fichier LICENSE à la racine du dépôt pour le texte complet.
|
|
|
|
"""Configuration du serveur Noxely, lue de l'environnement.
|
|
|
|
Aucune valeur sensible n'est écrite dans le dépôt : le jeton et les chemins viennent d'un
|
|
fichier .env local, à l'image des autres services de la maison.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import secrets
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
|
|
def _persisted_token(data_dir: Path) -> str:
|
|
"""Jeton auto-généré, conservé entre deux démarrages.
|
|
|
|
Un jeton tiré à chaque lancement obligerait à reconfigurer le téléphone après le moindre
|
|
redémarrage : il est donc écrit une fois, en lecture seule pour son propriétaire.
|
|
"""
|
|
path = data_dir / "token"
|
|
if path.exists():
|
|
existing = path.read_text().strip()
|
|
if existing:
|
|
return existing
|
|
token = secrets.token_urlsafe(24)
|
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(token + "\n")
|
|
path.chmod(0o600)
|
|
print(f"[noxely] jeton généré et conservé dans {path}")
|
|
return token
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Config:
|
|
data_dir: Path
|
|
token: str
|
|
host: str
|
|
port: int
|
|
#: Nombre de stations balayées à chaque passage du guet.
|
|
watch_stations: int
|
|
#: Pause entre deux balayages, en secondes. Le balayage est continu : cette pause n'est
|
|
#: qu'un garde-fou pour ne pas marteler l'annuaire si un tour échoue instantanément.
|
|
watch_interval: int
|
|
#: Stations sondées en parallèle. Plus il est haut, plus un tour complet est bref.
|
|
watch_concurrency: int
|
|
#: Garder la trace de ce que les stations annoncent, même sans guet en cours.
|
|
history: bool
|
|
#: Pause entre deux tours quand rien n'est guetté : l'historique n'a pas besoin de la
|
|
#: cadence d'un guet, et cela ménage la bande passante.
|
|
listen_interval: int
|
|
#: Ancienneté maximale de l'historique, en jours. Au-delà, les lignes sont purgées.
|
|
history_days: int
|
|
|
|
@property
|
|
def media_dir(self) -> Path:
|
|
return self.data_dir / "media"
|
|
|
|
@property
|
|
def db_path(self) -> Path:
|
|
return self.data_dir / "noxely.sqlite3"
|
|
|
|
|
|
def load() -> Config:
|
|
data_dir = Path(os.environ.get("NOXELY_DATA", "./data")).expanduser().resolve()
|
|
token = os.environ.get("NOXELY_TOKEN", "").strip() or _persisted_token(data_dir)
|
|
return Config(
|
|
data_dir=data_dir,
|
|
token=token,
|
|
host=os.environ.get("NOXELY_HOST", "0.0.0.0"),
|
|
port=int(os.environ.get("NOXELY_PORT", "8710")),
|
|
watch_stations=int(os.environ.get("NOXELY_WATCH_STATIONS", "40")),
|
|
watch_interval=int(os.environ.get("NOXELY_WATCH_INTERVAL", "5")),
|
|
watch_concurrency=int(os.environ.get("NOXELY_WATCH_CONCURRENCY", "8")),
|
|
history=os.environ.get("NOXELY_HISTORY", "1").strip().lower() not in ("0", "false", "non", "no"),
|
|
listen_interval=int(os.environ.get("NOXELY_LISTEN_INTERVAL", "60")),
|
|
history_days=int(os.environ.get("NOXELY_HISTORY_DAYS", "90")),
|
|
)
|