Compare commits
10 Commits
fdba2e9afc
...
1975a6f1d0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1975a6f1d0 | ||
|
|
729cb649fc | ||
|
|
14512aa3f4 | ||
|
|
4d5119ab5a | ||
|
|
9173aed76f | ||
|
|
3f09ff2bd6 | ||
|
|
221e1ad318 | ||
|
|
4246d13339 | ||
|
|
86fa13a1a0 | ||
|
|
77bd4fabb6 |
@@ -18,7 +18,7 @@
|
||||
"channel": 3,
|
||||
"name": "Talkback",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"level_db": -60.0,
|
||||
"pan": 0
|
||||
},
|
||||
"4": {
|
||||
@@ -31,15 +31,15 @@
|
||||
"5": {
|
||||
"channel": 5,
|
||||
"name": "Vox1",
|
||||
"on": true,
|
||||
"level_db": -1.1,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"6": {
|
||||
"channel": 6,
|
||||
"name": "Vox2",
|
||||
"on": true,
|
||||
"level_db": -0.15,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"7": {
|
||||
@@ -59,50 +59,50 @@
|
||||
"9": {
|
||||
"channel": 9,
|
||||
"name": "Kick",
|
||||
"on": true,
|
||||
"level_db": -5.2,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"10": {
|
||||
"channel": 10,
|
||||
"name": "Snare",
|
||||
"on": true,
|
||||
"level_db": -9.95,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"11": {
|
||||
"channel": 11,
|
||||
"name": "Tom 1",
|
||||
"on": true,
|
||||
"level_db": -16.6,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"12": {
|
||||
"channel": 12,
|
||||
"name": "Tom 2",
|
||||
"on": true,
|
||||
"level_db": -18.0,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"13": {
|
||||
"channel": 13,
|
||||
"name": "Tom3",
|
||||
"on": true,
|
||||
"level_db": -24.0,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"14": {
|
||||
"channel": 14,
|
||||
"name": "Pan SX",
|
||||
"on": true,
|
||||
"level_db": -15.08,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"15": {
|
||||
"channel": 15,
|
||||
"name": "Pan dx",
|
||||
"on": true,
|
||||
"level_db": -14.68,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"16": {
|
||||
@@ -123,21 +123,21 @@
|
||||
"channel": 18,
|
||||
"name": "Archetto",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"level_db": -76.0,
|
||||
"pan": 0
|
||||
},
|
||||
"19": {
|
||||
"channel": 19,
|
||||
"name": "Vox 5",
|
||||
"on": true,
|
||||
"level_db": -0.1,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"20": {
|
||||
"channel": 20,
|
||||
"name": "Tast",
|
||||
"on": true,
|
||||
"level_db": 0.0,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"21": {
|
||||
@@ -157,15 +157,15 @@
|
||||
"23": {
|
||||
"channel": 23,
|
||||
"name": " Vox3",
|
||||
"on": true,
|
||||
"level_db": -0.2,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"24": {
|
||||
"channel": 24,
|
||||
"name": "Chit cnt",
|
||||
"on": true,
|
||||
"level_db": -1.75,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"25": {
|
||||
@@ -185,7 +185,7 @@
|
||||
"27": {
|
||||
"channel": 27,
|
||||
"name": "Vox 4",
|
||||
"on": true,
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
@@ -228,14 +228,14 @@
|
||||
"channel": 33,
|
||||
"name": "PC",
|
||||
"on": true,
|
||||
"level_db": -20.4,
|
||||
"pan": -63
|
||||
"level_db": -17.5,
|
||||
"pan": 0
|
||||
},
|
||||
"34": {
|
||||
"channel": 34,
|
||||
"name": "PC",
|
||||
"on": true,
|
||||
"level_db": -20.4,
|
||||
"level_db": -17.5,
|
||||
"pan": 63
|
||||
},
|
||||
"35": {
|
||||
@@ -286,7 +286,7 @@
|
||||
"mix": 1,
|
||||
"name": "Aux 1",
|
||||
"on": true,
|
||||
"level_db": -1.55
|
||||
"level_db": -11.1
|
||||
},
|
||||
"2": {
|
||||
"mix": 2,
|
||||
@@ -298,37 +298,37 @@
|
||||
"mix": 3,
|
||||
"name": "Aux 3",
|
||||
"on": true,
|
||||
"level_db": -0.5
|
||||
"level_db": 0.35
|
||||
},
|
||||
"4": {
|
||||
"mix": 4,
|
||||
"name": "Aux 4",
|
||||
"on": true,
|
||||
"level_db": -0.6
|
||||
"level_db": 0.0
|
||||
},
|
||||
"5": {
|
||||
"mix": 5,
|
||||
"name": "batteria",
|
||||
"on": true,
|
||||
"level_db": 1.35
|
||||
"level_db": -4.9
|
||||
},
|
||||
"6": {
|
||||
"mix": 6,
|
||||
"name": "Aux 6",
|
||||
"on": true,
|
||||
"level_db": -6.65
|
||||
"level_db": -9.3
|
||||
},
|
||||
"7": {
|
||||
"mix": 7,
|
||||
"name": "Aux 7",
|
||||
"on": true,
|
||||
"level_db": -2.7
|
||||
"level_db": -6.35
|
||||
},
|
||||
"8": {
|
||||
"mix": 8,
|
||||
"name": "Aux 8",
|
||||
"on": true,
|
||||
"level_db": -2.65
|
||||
"level_db": -9.0
|
||||
},
|
||||
"9": {
|
||||
"mix": 9,
|
||||
@@ -382,13 +382,13 @@
|
||||
"mix": 17,
|
||||
"name": "Diretta",
|
||||
"on": true,
|
||||
"level_db": 0.0
|
||||
"level_db": -1.0
|
||||
},
|
||||
"18": {
|
||||
"mix": 18,
|
||||
"name": "Diretta",
|
||||
"on": true,
|
||||
"level_db": 0.0
|
||||
"level_db": -1.0
|
||||
},
|
||||
"19": {
|
||||
"mix": 19,
|
||||
@@ -403,5 +403,12 @@
|
||||
"level_db": 6.0
|
||||
}
|
||||
},
|
||||
"timestamp": 1762193941.3288717
|
||||
"timestamp": 1770577942.7806144,
|
||||
"steinch": {},
|
||||
"fxrtn": {},
|
||||
"dcas": {},
|
||||
"matrices": {},
|
||||
"stereo": {},
|
||||
"mono": {},
|
||||
"mute_masters": {}
|
||||
}
|
||||
4
.tf5_mixer_cache/mixer_ip.json
Normal file
4
.tf5_mixer_cache/mixer_ip.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"ip": "192.168.1.59",
|
||||
"timestamp": 1771271930.0475464
|
||||
}
|
||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
# Dockerfile
|
||||
|
||||
# 1. Usa un'immagine Python ufficiale e leggera come base
|
||||
FROM python:3.11-slim
|
||||
|
||||
# 2. Imposta una directory di lavoro all'interno del container
|
||||
WORKDIR /app
|
||||
|
||||
# 3. Copia il file delle dipendenze e installale
|
||||
# Questo passaggio viene fatto prima per sfruttare la cache di Docker.
|
||||
# Se requirements.txt non cambia, questo strato non verrà ricostruito.
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 4. Copia tutti i file del progetto nella directory di lavoro del container
|
||||
COPY . .
|
||||
|
||||
# 5. Comando da eseguire quando il container viene avviato
|
||||
CMD ["python", "telegram_bot.py"]
|
||||
Binary file not shown.
Binary file not shown.
407
cache/mixer_state.json
vendored
Normal file
407
cache/mixer_state.json
vendored
Normal file
@@ -0,0 +1,407 @@
|
||||
{
|
||||
"channels": {
|
||||
"1": {
|
||||
"channel": 1,
|
||||
"name": "Sconosciuto",
|
||||
"on": false,
|
||||
"level_db": 0.65,
|
||||
"pan": 0
|
||||
},
|
||||
"2": {
|
||||
"channel": 2,
|
||||
"name": "Gelato",
|
||||
"on": true,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"3": {
|
||||
"channel": 3,
|
||||
"name": "Talkback",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"4": {
|
||||
"channel": 4,
|
||||
"name": "Sconosciuto",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"5": {
|
||||
"channel": 5,
|
||||
"name": "Vox1",
|
||||
"on": true,
|
||||
"level_db": -13.15,
|
||||
"pan": 0
|
||||
},
|
||||
"6": {
|
||||
"channel": 6,
|
||||
"name": "Vox2",
|
||||
"on": true,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"7": {
|
||||
"channel": 7,
|
||||
"name": "Basso",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"8": {
|
||||
"channel": 8,
|
||||
"name": "Sconosciuto",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"9": {
|
||||
"channel": 9,
|
||||
"name": "Kick",
|
||||
"on": true,
|
||||
"level_db": -5.2,
|
||||
"pan": 0
|
||||
},
|
||||
"10": {
|
||||
"channel": 10,
|
||||
"name": "Snare",
|
||||
"on": true,
|
||||
"level_db": -9.95,
|
||||
"pan": 0
|
||||
},
|
||||
"11": {
|
||||
"channel": 11,
|
||||
"name": "Tom 1",
|
||||
"on": true,
|
||||
"level_db": -16.6,
|
||||
"pan": 0
|
||||
},
|
||||
"12": {
|
||||
"channel": 12,
|
||||
"name": "Tom 2",
|
||||
"on": true,
|
||||
"level_db": -3.45,
|
||||
"pan": 0
|
||||
},
|
||||
"13": {
|
||||
"channel": 13,
|
||||
"name": "Tom3",
|
||||
"on": true,
|
||||
"level_db": -24.0,
|
||||
"pan": 0
|
||||
},
|
||||
"14": {
|
||||
"channel": 14,
|
||||
"name": "Pan SX",
|
||||
"on": true,
|
||||
"level_db": -15.08,
|
||||
"pan": 0
|
||||
},
|
||||
"15": {
|
||||
"channel": 15,
|
||||
"name": "Pan dx",
|
||||
"on": true,
|
||||
"level_db": -14.68,
|
||||
"pan": 0
|
||||
},
|
||||
"16": {
|
||||
"channel": 16,
|
||||
"name": "Sconosciuto",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"17": {
|
||||
"channel": 17,
|
||||
"name": "Sconosciuto",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"18": {
|
||||
"channel": 18,
|
||||
"name": "Archetto",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"19": {
|
||||
"channel": 19,
|
||||
"name": "Vox 5",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"20": {
|
||||
"channel": 20,
|
||||
"name": "Tast",
|
||||
"on": true,
|
||||
"level_db": -10.8,
|
||||
"pan": 0
|
||||
},
|
||||
"21": {
|
||||
"channel": 21,
|
||||
"name": "Sconosciuto",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"22": {
|
||||
"channel": 22,
|
||||
"name": "Sconosciuto",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"23": {
|
||||
"channel": 23,
|
||||
"name": " Vox3",
|
||||
"on": true,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"24": {
|
||||
"channel": 24,
|
||||
"name": "Chit cnt",
|
||||
"on": true,
|
||||
"level_db": -0.5,
|
||||
"pan": 0
|
||||
},
|
||||
"25": {
|
||||
"channel": 25,
|
||||
"name": "Chit dx",
|
||||
"on": true,
|
||||
"level_db": -22.7,
|
||||
"pan": 0
|
||||
},
|
||||
"26": {
|
||||
"channel": 26,
|
||||
"name": "Sconosciuto",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"27": {
|
||||
"channel": 27,
|
||||
"name": "Vox 4",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"28": {
|
||||
"channel": 28,
|
||||
"name": "Sconosciuto",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"29": {
|
||||
"channel": 29,
|
||||
"name": "Pad",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"30": {
|
||||
"channel": 30,
|
||||
"name": "Sconosciuto",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"31": {
|
||||
"channel": 31,
|
||||
"name": "Sconosciuto",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"32": {
|
||||
"channel": 32,
|
||||
"name": "Sconosciuto",
|
||||
"on": false,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"33": {
|
||||
"channel": 33,
|
||||
"name": "PC",
|
||||
"on": true,
|
||||
"level_db": -17.5,
|
||||
"pan": -63
|
||||
},
|
||||
"34": {
|
||||
"channel": 34,
|
||||
"name": "PC",
|
||||
"on": true,
|
||||
"level_db": -17.5,
|
||||
"pan": 63
|
||||
},
|
||||
"35": {
|
||||
"channel": 35,
|
||||
"name": "ch35",
|
||||
"on": true,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"36": {
|
||||
"channel": 36,
|
||||
"name": "ch36",
|
||||
"on": true,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"37": {
|
||||
"channel": 37,
|
||||
"name": "ch37",
|
||||
"on": true,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"38": {
|
||||
"channel": 38,
|
||||
"name": "ch38",
|
||||
"on": true,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"39": {
|
||||
"channel": 39,
|
||||
"name": "ch39",
|
||||
"on": true,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
},
|
||||
"40": {
|
||||
"channel": 40,
|
||||
"name": "ch40",
|
||||
"on": true,
|
||||
"level_db": null,
|
||||
"pan": 0
|
||||
}
|
||||
},
|
||||
"mixes": {
|
||||
"1": {
|
||||
"mix": 1,
|
||||
"name": "Aux 1",
|
||||
"on": true,
|
||||
"level_db": -1.55
|
||||
},
|
||||
"2": {
|
||||
"mix": 2,
|
||||
"name": "Aux 2",
|
||||
"on": true,
|
||||
"level_db": -0.05
|
||||
},
|
||||
"3": {
|
||||
"mix": 3,
|
||||
"name": "Aux 3",
|
||||
"on": true,
|
||||
"level_db": -0.5
|
||||
},
|
||||
"4": {
|
||||
"mix": 4,
|
||||
"name": "Aux 4",
|
||||
"on": true,
|
||||
"level_db": -0.6
|
||||
},
|
||||
"5": {
|
||||
"mix": 5,
|
||||
"name": "batteria",
|
||||
"on": true,
|
||||
"level_db": 1.35
|
||||
},
|
||||
"6": {
|
||||
"mix": 6,
|
||||
"name": "Aux 6",
|
||||
"on": true,
|
||||
"level_db": -6.65
|
||||
},
|
||||
"7": {
|
||||
"mix": 7,
|
||||
"name": "Aux 7",
|
||||
"on": true,
|
||||
"level_db": -2.7
|
||||
},
|
||||
"8": {
|
||||
"mix": 8,
|
||||
"name": "Aux 8",
|
||||
"on": true,
|
||||
"level_db": -2.65
|
||||
},
|
||||
"9": {
|
||||
"mix": 9,
|
||||
"name": "Aux 9/10",
|
||||
"on": true,
|
||||
"level_db": -8.1
|
||||
},
|
||||
"10": {
|
||||
"mix": 10,
|
||||
"name": "Aux 9/10",
|
||||
"on": true,
|
||||
"level_db": -8.1
|
||||
},
|
||||
"11": {
|
||||
"mix": 11,
|
||||
"name": "Aux Pulp",
|
||||
"on": true,
|
||||
"level_db": -10.3
|
||||
},
|
||||
"12": {
|
||||
"mix": 12,
|
||||
"name": "Aux Pulp",
|
||||
"on": true,
|
||||
"level_db": -10.3
|
||||
},
|
||||
"13": {
|
||||
"mix": 13,
|
||||
"name": "Aux13/14",
|
||||
"on": false,
|
||||
"level_db": -1.6
|
||||
},
|
||||
"14": {
|
||||
"mix": 14,
|
||||
"name": "Aux13/14",
|
||||
"on": false,
|
||||
"level_db": -1.6
|
||||
},
|
||||
"15": {
|
||||
"mix": 15,
|
||||
"name": "Aux15/16",
|
||||
"on": true,
|
||||
"level_db": 0.1
|
||||
},
|
||||
"16": {
|
||||
"mix": 16,
|
||||
"name": "Aux15/16",
|
||||
"on": true,
|
||||
"level_db": 0.1
|
||||
},
|
||||
"17": {
|
||||
"mix": 17,
|
||||
"name": "Diretta",
|
||||
"on": true,
|
||||
"level_db": 0.0
|
||||
},
|
||||
"18": {
|
||||
"mix": 18,
|
||||
"name": "Diretta",
|
||||
"on": true,
|
||||
"level_db": 0.0
|
||||
},
|
||||
"19": {
|
||||
"mix": 19,
|
||||
"name": "Traduzio",
|
||||
"on": true,
|
||||
"level_db": 6.0
|
||||
},
|
||||
"20": {
|
||||
"mix": 20,
|
||||
"name": "Traduzio",
|
||||
"on": true,
|
||||
"level_db": 6.0
|
||||
}
|
||||
},
|
||||
"timestamp": 1762800269.2657
|
||||
}
|
||||
247
config.py
247
config.py
@@ -1,13 +1,252 @@
|
||||
import socket
|
||||
import ipaddress
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import json
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
import time
|
||||
|
||||
# Configurazione mixer
|
||||
DEFAULT_HOST = "192.168.1.62"
|
||||
DEFAULT_PORT = 49280
|
||||
TF5_INPUT_CHANNELS = 40
|
||||
TF5_MIX_BUSSES = 20
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Configurazione cache
|
||||
CACHE_DIR = Path(".tf5_mixer_cache")
|
||||
CACHE_FILE = CACHE_DIR / "channels_cache.json"
|
||||
IP_CACHE_FILE = CACHE_DIR / "mixer_ip.json"
|
||||
CACHE_DURATION = 3600 # 60 minuti in secondi
|
||||
IP_CACHE_DURATION = 300 # 5 minuti per l'IP
|
||||
|
||||
def log(message: str, level: str = "INFO"):
|
||||
"""Stampa log formattato con timestamp"""
|
||||
timestamp = time.strftime("%H:%M:%S")
|
||||
symbols = {
|
||||
"INFO": "ℹ️",
|
||||
"SUCCESS": "✅",
|
||||
"ERROR": "❌",
|
||||
"WARNING": "⚠️",
|
||||
"SEARCH": "🔍",
|
||||
"NETWORK": "🌐",
|
||||
"CACHE": "📍",
|
||||
"TIME": "⏱️"
|
||||
}
|
||||
symbol = symbols.get(level, "•")
|
||||
print(f"[{timestamp}] {symbol} {message}")
|
||||
|
||||
def get_local_network() -> str:
|
||||
"""Ottiene la rete locale del sistema"""
|
||||
log("Rilevamento rete locale in corso...", "NETWORK")
|
||||
try:
|
||||
# Crea un socket per ottenere l'IP locale
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.connect(("8.8.8.8", 80))
|
||||
local_ip = s.getsockname()[0]
|
||||
s.close()
|
||||
|
||||
# Converte in network (es: 192.168.1.0/24)
|
||||
ip_parts = local_ip.split('.')
|
||||
network = f"{ip_parts[0]}.{ip_parts[1]}.{ip_parts[2]}.0/24"
|
||||
|
||||
log(f"IP locale rilevato: {local_ip}", "INFO")
|
||||
log(f"Rete da scansionare: {network}", "NETWORK")
|
||||
return network
|
||||
except Exception as e:
|
||||
log(f"Errore nel rilevare la rete locale: {e}", "ERROR")
|
||||
network = "192.168.1.0/24"
|
||||
log(f"Uso rete di fallback: {network}", "WARNING")
|
||||
return network
|
||||
|
||||
def check_port(ip: str, port: int, timeout: float = 0.5) -> bool:
|
||||
"""Controlla se una porta è aperta su un IP"""
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(timeout)
|
||||
result = sock.connect_ex((ip, port))
|
||||
sock.close()
|
||||
return result == 0
|
||||
except Exception as e:
|
||||
# Log solo per debug, commentabile se troppo verbose
|
||||
# log(f"Errore su {ip}: {e}", "ERROR")
|
||||
return False
|
||||
|
||||
def scan_network_for_mixer(port: int = DEFAULT_PORT, max_workers: int = 50) -> Optional[str]:
|
||||
"""Scansiona la rete locale per trovare il mixer"""
|
||||
log(f"Ricerca mixer sulla porta {port}...", "SEARCH")
|
||||
start_time = time.time()
|
||||
|
||||
network = get_local_network()
|
||||
|
||||
# Genera lista di IP da scansionare
|
||||
ip_network = ipaddress.ip_network(network, strict=False)
|
||||
total_hosts = sum(1 for _ in ip_network.hosts())
|
||||
log(f"Indirizzi da scansionare: {total_hosts}", "INFO")
|
||||
log(f"Thread paralleli: {max_workers}", "INFO")
|
||||
|
||||
found_ip = None
|
||||
scanned = 0
|
||||
|
||||
# Scansione parallela per velocità
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
future_to_ip = {
|
||||
executor.submit(check_port, str(ip), port): str(ip)
|
||||
for ip in ip_network.hosts()
|
||||
}
|
||||
|
||||
log("Scansione in corso...", "SEARCH")
|
||||
|
||||
for future in as_completed(future_to_ip):
|
||||
ip = future_to_ip[future]
|
||||
scanned += 1
|
||||
|
||||
# Log di progresso ogni 50 IP
|
||||
if scanned % 50 == 0:
|
||||
log(f"Progresso: {scanned}/{total_hosts} IP scansionati", "INFO")
|
||||
|
||||
try:
|
||||
if future.result():
|
||||
found_ip = ip
|
||||
elapsed = time.time() - start_time
|
||||
log(f"MIXER TROVATO su {ip}:{port}", "SUCCESS")
|
||||
log(f"Tempo di scansione: {elapsed:.2f}s", "TIME")
|
||||
log(f"IP scansionati: {scanned}/{total_hosts}", "INFO")
|
||||
# Cancella i task rimanenti
|
||||
executor.shutdown(wait=False, cancel_futures=True)
|
||||
break
|
||||
except Exception as e:
|
||||
log(f"Errore durante scansione di {ip}: {e}", "ERROR")
|
||||
|
||||
if not found_ip:
|
||||
elapsed = time.time() - start_time
|
||||
log(f"Nessun mixer trovato sulla porta {port}", "ERROR")
|
||||
log(f"Tempo totale di scansione: {elapsed:.2f}s", "TIME")
|
||||
log(f"IP scansionati: {scanned}/{total_hosts}", "INFO")
|
||||
|
||||
return found_ip
|
||||
|
||||
def load_cached_ip() -> Optional[str]:
|
||||
"""Carica l'IP dalla cache se valido"""
|
||||
log("Controllo cache IP...", "CACHE")
|
||||
|
||||
if not IP_CACHE_FILE.exists():
|
||||
log("Nessuna cache trovata", "INFO")
|
||||
return None
|
||||
|
||||
try:
|
||||
with open(IP_CACHE_FILE, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
cached_ip = data.get('ip')
|
||||
cache_time = data.get('timestamp', 0)
|
||||
age = time.time() - cache_time
|
||||
|
||||
log(f"Cache trovata: {cached_ip} (età: {int(age)}s)", "CACHE")
|
||||
|
||||
# Controlla se la cache è ancora valida
|
||||
if age < IP_CACHE_DURATION:
|
||||
log(f"Verifica raggiungibilità di {cached_ip}...", "CACHE")
|
||||
# Verifica che l'IP sia ancora valido
|
||||
if cached_ip and check_port(cached_ip, DEFAULT_PORT, timeout=1.0):
|
||||
log(f"IP dalla cache valido: {cached_ip}", "SUCCESS")
|
||||
return cached_ip
|
||||
else:
|
||||
log(f"IP dalla cache non raggiungibile", "WARNING")
|
||||
else:
|
||||
log(f"Cache scaduta (max {IP_CACHE_DURATION}s)", "WARNING")
|
||||
except Exception as e:
|
||||
log(f"Errore nel leggere cache IP: {e}", "ERROR")
|
||||
|
||||
return None
|
||||
|
||||
def save_ip_to_cache(ip: str):
|
||||
"""Salva l'IP nella cache"""
|
||||
log(f"Salvataggio IP in cache: {ip}", "CACHE")
|
||||
CACHE_DIR.mkdir(exist_ok=True)
|
||||
|
||||
try:
|
||||
with open(IP_CACHE_FILE, 'w') as f:
|
||||
json.dump({
|
||||
'ip': ip,
|
||||
'timestamp': time.time()
|
||||
}, f, indent=2)
|
||||
log(f"Cache salvata: {IP_CACHE_FILE}", "SUCCESS")
|
||||
except Exception as e:
|
||||
log(f"Errore nel salvare cache IP: {e}", "ERROR")
|
||||
|
||||
def get_mixer_ip() -> str:
|
||||
"""
|
||||
Ottiene l'IP del mixer con auto-discovery:
|
||||
1. Controlla cache
|
||||
2. Scansiona la rete
|
||||
3. Fallback su IP predefinito
|
||||
"""
|
||||
log("=== INIZIO RICERCA MIXER ===", "INFO")
|
||||
|
||||
# Prova con la cache
|
||||
cached_ip = load_cached_ip()
|
||||
if cached_ip:
|
||||
log("=== RICERCA COMPLETATA (CACHE) ===", "SUCCESS")
|
||||
return cached_ip
|
||||
|
||||
# Scansiona la rete
|
||||
log("Avvio scansione di rete...", "SEARCH")
|
||||
discovered_ip = scan_network_for_mixer()
|
||||
if discovered_ip:
|
||||
save_ip_to_cache(discovered_ip)
|
||||
log("=== RICERCA COMPLETATA (DISCOVERY) ===", "SUCCESS")
|
||||
return discovered_ip
|
||||
|
||||
# Fallback
|
||||
fallback_ip = "192.168.1.57"
|
||||
log(f"Uso IP predefinito di fallback: {fallback_ip}", "WARNING")
|
||||
log("=== RICERCA COMPLETATA (FALLBACK) ===", "WARNING")
|
||||
return fallback_ip
|
||||
|
||||
# Variabile globale per l'host (si aggiorna automaticamente)
|
||||
DEFAULT_HOST = None
|
||||
|
||||
def get_host() -> str:
|
||||
"""Ottiene l'host del mixer (con lazy loading)"""
|
||||
global DEFAULT_HOST
|
||||
if DEFAULT_HOST is None:
|
||||
log("Host mixer non inizializzato, avvio discovery...", "INFO")
|
||||
DEFAULT_HOST = get_mixer_ip()
|
||||
else:
|
||||
log(f"Host mixer già inizializzato: {DEFAULT_HOST}", "INFO")
|
||||
return DEFAULT_HOST
|
||||
|
||||
def refresh_mixer_ip():
|
||||
"""Forza un refresh dell'IP del mixer"""
|
||||
global DEFAULT_HOST
|
||||
log("=== REFRESH FORZATO IP MIXER ===", "INFO")
|
||||
log("Cancellazione cache esistente...", "CACHE")
|
||||
|
||||
# Rimuovi cache
|
||||
if IP_CACHE_FILE.exists():
|
||||
try:
|
||||
IP_CACHE_FILE.unlink()
|
||||
log("Cache rimossa", "SUCCESS")
|
||||
except Exception as e:
|
||||
log(f"Errore rimozione cache: {e}", "ERROR")
|
||||
|
||||
# Nuova scansione
|
||||
DEFAULT_HOST = scan_network_for_mixer()
|
||||
if DEFAULT_HOST:
|
||||
save_ip_to_cache(DEFAULT_HOST)
|
||||
log(f"Nuovo IP impostato: {DEFAULT_HOST}", "SUCCESS")
|
||||
else:
|
||||
log("Refresh fallito, mantengo IP precedente", "ERROR")
|
||||
|
||||
log("=== REFRESH COMPLETATO ===", "INFO")
|
||||
return DEFAULT_HOST
|
||||
|
||||
# Test rapido
|
||||
if __name__ == "__main__":
|
||||
log("=== TEST AUTO-DISCOVERY MIXER ===", "INFO")
|
||||
host = get_host()
|
||||
log(f"Host finale: {host}", "SUCCESS")
|
||||
|
||||
# Test refresh
|
||||
# log("\n=== TEST REFRESH ===", "INFO")
|
||||
# refresh_mixer_ip()
|
||||
21
conversations/1073121360.json
Normal file
21
conversations/1073121360.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"user_id": 1073121360,
|
||||
"username": "Ingdavidfeliz",
|
||||
"last_updated": 1762197549.52935,
|
||||
"last_updated_str": "2025-11-03 19:19:09",
|
||||
"message_count": 2,
|
||||
"history": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Alzami il basso",
|
||||
"timestamp": 1762197547.3495104,
|
||||
"timestamp_str": "2025-11-03 19:19:07"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Ciao David! Per \"alzami il basso\", intendi nel tuo monitor personale o in un altro mix? Ho bisogno di sapere in quale mix vuoi che alzi il basso.",
|
||||
"timestamp": 1762197549.5293229,
|
||||
"timestamp_str": "2025-11-03 19:19:09"
|
||||
}
|
||||
]
|
||||
}
|
||||
93
conversations/309700430.json
Normal file
93
conversations/309700430.json
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"user_id": 309700430,
|
||||
"username": "Cicciomt7",
|
||||
"last_updated": 1762198991.2953947,
|
||||
"last_updated_str": "2025-11-03 19:43:11",
|
||||
"message_count": 14,
|
||||
"history": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Chiudi vox 5",
|
||||
"timestamp": 1762198915.897361,
|
||||
"timestamp_str": "2025-11-03 19:41:55"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Ok, Vox 5 (canale 19) è spento. 🎤❌",
|
||||
"timestamp": 1762198920.8823922,
|
||||
"timestamp_str": "2025-11-03 19:42:00"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Apri vox 5",
|
||||
"timestamp": 1762198929.1841967,
|
||||
"timestamp_str": "2025-11-03 19:42:09"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Ok, Vox 5 (canale 19) è acceso. 🎤✅",
|
||||
"timestamp": 1762198931.5372686,
|
||||
"timestamp_str": "2025-11-03 19:42:11"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Chiusi vox 3",
|
||||
"timestamp": 1762198935.3993735,
|
||||
"timestamp_str": "2025-11-03 19:42:15"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Non ho trovato nessun canale chiamato \"vox 3\". Sei sicuro del nome? Forse intendevi un altro canale o un numero?",
|
||||
"timestamp": 1762198938.0440562,
|
||||
"timestamp_str": "2025-11-03 19:42:18"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Vox3",
|
||||
"timestamp": 1762198948.152694,
|
||||
"timestamp_str": "2025-11-03 19:42:28"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Ok, \" Vox3\" (canale 23) è spento. 🎤❌",
|
||||
"timestamp": 1762198953.3262546,
|
||||
"timestamp_str": "2025-11-03 19:42:33"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Apri vox3",
|
||||
"timestamp": 1762198964.16481,
|
||||
"timestamp_str": "2025-11-03 19:42:44"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Ok, Vox3 (canale 23) è acceso. 🎤✅",
|
||||
"timestamp": 1762198966.712192,
|
||||
"timestamp_str": "2025-11-03 19:42:46"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Chiudi il gelato",
|
||||
"timestamp": 1762198972.835868,
|
||||
"timestamp_str": "2025-11-03 19:42:52"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Ok, \"Gelato\" (canale 2) è spento. 🎤❌",
|
||||
"timestamp": 1762198975.563454,
|
||||
"timestamp_str": "2025-11-03 19:42:55"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Riaprilo",
|
||||
"timestamp": 1762198989.0117595,
|
||||
"timestamp_str": "2025-11-03 19:43:09"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Ok, \"Gelato\" (canale 2) è acceso. 🎤✅",
|
||||
"timestamp": 1762198991.295375,
|
||||
"timestamp_str": "2025-11-03 19:43:11"
|
||||
}
|
||||
]
|
||||
}
|
||||
21
conversations/436176546.json
Normal file
21
conversations/436176546.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"user_id": 436176546,
|
||||
"username": "Gloriushhh",
|
||||
"last_updated": 1762199858.7119617,
|
||||
"last_updated_str": "2025-11-03 19:57:38",
|
||||
"message_count": 2,
|
||||
"history": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Alza il volume del mio canale",
|
||||
"timestamp": 1762199853.7008705,
|
||||
"timestamp_str": "2025-11-03 19:57:33"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Non riesco a trovare il tuo canale 'VOX 2'. Sei sicuro che si chiami così? Potrebbe esserci un nome leggermente diverso o il canale non è presente.",
|
||||
"timestamp": 1762199858.7119193,
|
||||
"timestamp_str": "2025-11-03 19:57:38"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,87 +1,135 @@
|
||||
{
|
||||
"user_id": 4762864,
|
||||
"username": "unnikked",
|
||||
"last_updated": 1762194065.7709692,
|
||||
"last_updated_str": "2025-11-03 19:21:05",
|
||||
"message_count": 13,
|
||||
"last_updated": 1770578542.7348943,
|
||||
"last_updated_str": "2026-02-08 19:22:22",
|
||||
"message_count": 21,
|
||||
"history": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Me lo spegni il gelato",
|
||||
"timestamp": 1761595234.0849943,
|
||||
"timestamp_str": "2025-10-27 21:00:34"
|
||||
"content": "Controlla ora",
|
||||
"timestamp": 1769455908.2156205,
|
||||
"timestamp_str": "2026-01-26 19:31:48"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Fatto! Canale \"Gelato\" spento. ✅",
|
||||
"timestamp": 1761595239.1608367,
|
||||
"timestamp_str": "2025-10-27 21:00:39"
|
||||
"content": "Allora, Nicola, ho controllato di nuovo il mixer dopo il refresh! Ecco cosa vedo:\n\n**Canali:**\nCi sono un bel po' di canali attivi e nominati:\n* **Gelato (Canale 2):** Acceso, volume a -11.7 dB\n* **Vox1 (Canale 5):** Acceso, volume a 0 dB\n* **Vox2 (Canale 6):** Acceso, volume a 6.3 dB\n* **Basso (Canale 7):** Acceso, volume a -2.15 dB\n* **Kick (Canale 9):** Acceso, volume a -0.35 dB\n* **Snare (Canale 10):** Acceso, volume a -1.6 dB\n* **Tom 1 (Canale 11):** Acceso, volume a -2.8 dB\n* **Tom 2 (Canale 12):** Acceso, volume a -0.8 dB\n* **Tom3 (Canale 13):** Acceso, volume a -3.05 dB\n* **Pan SX (Canale 14):** Acceso, volume a -3.85 dB\n* **Pan dx (Canale 15):** Acceso, volume a -4.3 dB\n* **Archetto (Canale 18):** Acceso, volume a 4.6 dB\n* **Vox 5 (Canale 19):** Acceso, volume a -4.65 dB\n* **Tast (Canale 20):** Acceso, volume a -1.6 dB\n* **Vox3 (Canale 23):** Acceso, volume a 3.05 dB\n* **Chit cnt (Canale 24):** Acceso, volume a -2.35 dB\n* **Chit dx (Canale 25):** Acceso, volume a -0.85 dB\n* **PC (Canale 33 e 34):** Accesi, volume a -43.2 dB\n\n**Canali che sono accesi ma con volume bassissimo (-120 dB), quindi praticamente muti:**\n* **Vox 4 (Canale 27)**\n* **ch35 (Canale 35)**\n* **ch36 (Canale 36)**\n* **ch37 (Canale 37)**\n* **ch38 (Canale 38)**\n* **ch39 (Canale 39)**\n* **ch40 (Canale 40)**\n\n**Canali spenti o sconosciuti:**\n* **Canale 1:** Sconosciuto, spento, volume a 0.65 dB\n* **Talkback (Canale 3):** Spento, volume a -120 dB\n* **Canale 4:** Sconosciuto, spento, volume a -120 dB\n* **Canale 8:** Sconosciuto, spento, volume a -120 dB\n* **Canale 16:** Sconosciuto, spento, volume a -120 dB\n* **Canale 17:** Sconosciuto, spento, volume a -120 dB\n* **Canale 21:** Sconosciuto, spento, volume a -120 dB\n* **Canale 22:** Sconosciuto, spento, volume a -120 dB\n* **Canale 26:** Sconosciuto, spento, volume a -120 dB\n* **Canale 28:** Sconosciuto, spento, volume a -120 dB\n* **Pad (Canale 29):** Spento, volume a -120 dB\n* **Canali dal 30 al 32:** Sconosciuti, spenti, volume a -120 dB\n\n**Mix:**\nAnche i mix sono belli attivi:\n* **Aux 1 (Mix 1):** Acceso, volume a -11.1 dB\n* **Aux 2 (Mix 2):** Acceso, volume a -0.05 dB\n* **Aux 3 (Mix 3):** Acceso, volume a 0.35 dB\n* **Aux 4 (Mix 4):** Acceso, volume a 0 dB\n* **batteria (Mix 5):** Acceso, volume a 1.35 dB\n* **Aux 6 (Mix 6):** Acceso, volume a 10 dB\n* **Aux 7 (Mix 7):** Acceso, volume a -9.75 dB\n* **Aux 8 (Mix 8):** Acceso, volume a -4.4 dB\n* **Aux 9/10 (Mix 9 e 10):** Accesi, volume a -8.1 dB\n* **Aux Pulp (Mix 11 e 12):** Accesi, volume a -10.3 dB\n* **Aux15/16 (Mix 15 e 16):** Accesi, volume a 0.1 dB\n* **Diretta (Mix 17 e 18):** Accesi, volume a -2 dB\n* **Traduzio (Mix 19 e 20):** Accesi, volume a 6 dB\n\n**Mix spenti:**\n* **Aux13/14 (Mix 13 e 14):** Spenti, volume a -1.6 dB\n\nLa cache è stata aggiornata 5 secondi fa. Sembra che ci sia un buon movimento sul mixer! Fammi sapere se vuoi che faccia qualche modifica. 🎛️🎤",
|
||||
"timestamp": 1769455917.246521,
|
||||
"timestamp_str": "2026-01-26 19:31:57"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Mi accendi il gelato.",
|
||||
"timestamp": 1761595383.8880353,
|
||||
"timestamp_str": "2025-10-27 21:03:03"
|
||||
"content": "ciao",
|
||||
"timestamp": 1769766889.7665708,
|
||||
"timestamp_str": "2026-01-30 09:54:49"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Certo! Gelato acceso! 🍦✅",
|
||||
"timestamp": 1761595387.27931,
|
||||
"timestamp_str": "2025-10-27 21:03:07"
|
||||
"content": "Ciao Nicola! Tutto bene? Dimmi pure se hai bisogno di qualcosa con il mixer! 🎛️🎤",
|
||||
"timestamp": 1769766891.7504349,
|
||||
"timestamp_str": "2026-01-30 09:54:51"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Apaga el helado. Cappuccino.",
|
||||
"timestamp": 1761595426.0112157,
|
||||
"timestamp_str": "2025-10-27 21:03:46"
|
||||
"content": "è acceso?",
|
||||
"timestamp": 1769766900.066535,
|
||||
"timestamp_str": "2026-01-30 09:55:00"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Gelato spento! ✅\nNon ho trovato nessun canale chiamato \"Cappuccino\", sei sicuro del nome? 🤔",
|
||||
"timestamp": 1761595431.5373232,
|
||||
"timestamp_str": "2025-10-27 21:03:51"
|
||||
"content": "Cosa intendi? Un canale, un mix, o qualcos'altro? 🧐",
|
||||
"timestamp": 1769766901.4737165,
|
||||
"timestamp_str": "2026-01-30 09:55:01"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Okay, aprimi il gelato allora!",
|
||||
"timestamp": 1762193935.418264,
|
||||
"timestamp_str": "2025-11-03 19:18:55"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Gli alzi il gelato?",
|
||||
"timestamp": 1762193958.1497903,
|
||||
"timestamp_str": "2025-11-03 19:19:18"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Aprimi il giubbotto.",
|
||||
"timestamp": 1762194045.3195145,
|
||||
"timestamp_str": "2025-11-03 19:20:45"
|
||||
"content": "proprio il mixer, lo riesci a raggiungere?",
|
||||
"timestamp": 1769766912.3007145,
|
||||
"timestamp_str": "2026-01-30 09:55:12"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Non posso aprirti il giubbotto, non è un canale o un mix del mixer! 😄\n\nPosso aiutarti con i canali audio, i microfoni o i monitor, se vuoi.",
|
||||
"timestamp": 1762194046.8080468,
|
||||
"timestamp_str": "2025-11-03 19:20:46"
|
||||
"content": "✅ Certo, Nicola! Ho appena fatto un refresh e ho ricaricato tutte le informazioni dal mixer. Sembra che sia bello sveglio e raggiungibile! 🎛️✨",
|
||||
"timestamp": 1769768014.9555686,
|
||||
"timestamp_str": "2026-01-30 10:13:34"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Aprimi il gelato",
|
||||
"timestamp": 1762194053.0858698,
|
||||
"timestamp_str": "2025-11-03 19:20:53"
|
||||
"content": "System check",
|
||||
"timestamp": 1770577025.9354753,
|
||||
"timestamp_str": "2026-02-08 18:57:05"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "System check",
|
||||
"timestamp": 1770577350.9690044,
|
||||
"timestamp_str": "2026-02-08 19:02:30"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "System check",
|
||||
"timestamp": 1770577683.8676918,
|
||||
"timestamp_str": "2026-02-08 19:08:03"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "System check",
|
||||
"timestamp": 1770577775.3157346,
|
||||
"timestamp_str": "2026-02-08 19:09:35"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "System check",
|
||||
"timestamp": 1770577899.7554913,
|
||||
"timestamp_str": "2026-02-08 20:11:39"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "System check",
|
||||
"timestamp": 1770577940.3210933,
|
||||
"timestamp_str": "2026-02-08 20:12:20"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Certo! Gelato acceso! 🍦✅",
|
||||
"timestamp": 1762194054.6322277,
|
||||
"timestamp_str": "2025-11-03 19:20:54"
|
||||
"content": "✅ Fatto, Nicola! Ho appena fatto un controllo completo del sistema e ricaricato tutte le informazioni dal mixer. È bello sveglio e raggiungibile! 🎛️✨",
|
||||
"timestamp": 1770577943.5131805,
|
||||
"timestamp_str": "2026-02-08 20:12:23"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "È ancora chiuso",
|
||||
"timestamp": 1762194065.7709692,
|
||||
"timestamp_str": "2025-11-03 19:21:05"
|
||||
"content": "Ciao",
|
||||
"timestamp": 1770577995.903267,
|
||||
"timestamp_str": "2026-02-08 19:13:15"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Ciao Nicola! Tutto bene? Dimmi pure se hai bisogno di qualcosa con il mixer! 🎛️🎤",
|
||||
"timestamp": 1770577997.697525,
|
||||
"timestamp_str": "2026-02-08 19:13:17"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Ciao pupo",
|
||||
"timestamp": 1770578526.691519,
|
||||
"timestamp_str": "2026-02-08 19:22:06"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Ciao Nicola! Tutto bene? Dimmi pure se hai bisogno di qualcosa con il mixer! 🎛️🎤",
|
||||
"timestamp": 1770578528.1236699,
|
||||
"timestamp_str": "2026-02-08 19:22:08"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Check canali",
|
||||
"timestamp": 1770578537.6022112,
|
||||
"timestamp_str": "2026-02-08 19:22:17"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Allora, Nicola, ho controllato tutti i canali! Ecco la situazione attuale:\n\n**Canali Attivi (o con volume udibile):**\n* **PC (Canale 33 e 34):** Accesi, volume a -17.5 dB\n\n**Canali che sono accesi ma con volume bassissimo (-120 dB), quindi praticamente muti:**\n* **Gelato (Canale 2)**\n* **Vox1 (Canale 5)**\n* **Vox2 (Canale 6)**\n* **Basso (Canale 7)**\n* **Kick (Canale 9)**\n* **Snare (Canale 10)**\n* **Tom 1 (Canale 11)**\n* **Tom 2 (Canale 12)**\n* **Tom3 (Canale 13)**\n* **Pan SX (Canale 14)**\n* **Pan dx (Canale 15)**\n* **Vox 5 (Canale 19)**\n* **Tast (Canale 20)**\n* **Vox3 (Canale 23)**\n* **Chit cnt (Canale 24)**\n* **Chit dx (Canale 25)**\n* **Vox 4 (Canale 27)**\n* **ch35 (Canale 35)**\n* **ch36 (Canale 36)**\n* **ch37 (Canale 37)**\n* **ch38 (Canale 38)**\n* **ch39 (Canale 39)**\n* **ch40 (Canale 40)**\n\n**Canali spenti o sconosciuti:**\n* **Canale 1:** Sconosciuto, spento, volume a 0.65 dB\n* **Talkback (Canale 3):** Spento, volume a -60 dB\n* **Canale 4:** Sconosciuto, spento, volume a -120 dB\n* **Canale 8:** Sconosciuto, spento, volume a -120 dB\n* **Canale 16:** Sconosciuto, spento, volume a -120 dB\n* **Canale 17:** Sconosciuto, spento, volume a -120 dB\n* **Archetto (Canale 18):** Spento, volume a -76 dB\n* **Canale 21:** Sconosciuto, spento, volume a -120 dB\n* **Canale 22:** Sconosciuto, spento, volume a -120 dB\n* **Canale 26:** Sconosciuto, spento, volume a -120 dB\n* **Canale 28:** Sconosciuto, spento, volume a -120 dB\n* **Pad (Canale 29):** Spento, volume a -120 dB\n* **Canali dal 30 al 32:** Sconosciuti, spenti, volume a -120 dB\n\nLa cache è stata aggiornata circa 10 minuti fa. Sembra che molti canali siano spenti o con volumi molto bassi. Fammi sapere se vuoi che accenda o alzi qualcosa! 🎛️🎤",
|
||||
"timestamp": 1770578542.7348733,
|
||||
"timestamp_str": "2026-02-08 19:22:22"
|
||||
}
|
||||
]
|
||||
}
|
||||
35
docker-compose.yaml
Normal file
35
docker-compose.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
tf5-bot:
|
||||
# Costruisce l'immagine usando il Dockerfile nella cartella corrente
|
||||
build: .
|
||||
|
||||
# Nome del container per una facile identificazione
|
||||
container_name: tf5-telegram-bot
|
||||
|
||||
# Carica le variabili d'ambiente dal file .env
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
# Politica di riavvio:
|
||||
# 'unless-stopped' riavvia il container automaticamente all'avvio di Docker
|
||||
# (e in caso di crash), a meno che tu non lo abbia fermato manualmente.
|
||||
restart: unless-stopped
|
||||
|
||||
# Modalità di rete "host":
|
||||
# Questo è FONDAMENTALE. Permette al container di accedere direttamente
|
||||
# alla rete del tuo computer. Senza questo, il bot non potrebbe
|
||||
# "vedere" e connettersi al mixer TF5 che si trova sulla tua rete locale.
|
||||
network_mode: "host"
|
||||
|
||||
# Volumi:
|
||||
# Questo collega le cartelle del tuo computer (a sinistra dei due punti)
|
||||
# alle cartelle all'interno del container (a destra).
|
||||
# In questo modo, i profili, le conversazioni e la cache vengono salvati
|
||||
# sul tuo disco e non vengono persi se il container viene riavviato.
|
||||
volumes:
|
||||
- ./profiles:/app/profiles
|
||||
- ./conversations:/app/conversations
|
||||
- ./cache:/app/cache
|
||||
12
mixer_cache/tf5_cache.json
Normal file
12
mixer_cache/tf5_cache.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"channels": {},
|
||||
"mixes": {},
|
||||
"stereo_ins": {},
|
||||
"fx_returns": {},
|
||||
"dcas": {},
|
||||
"matrices": {},
|
||||
"stereo_out": {},
|
||||
"mono_out": {},
|
||||
"mute_masters": {},
|
||||
"timestamp": 0
|
||||
}
|
||||
2311
mixer_controller.py
2311
mixer_controller.py
File diff suppressed because it is too large
Load Diff
17
profiles/1552084582.json
Normal file
17
profiles/1552084582.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"user_id": 1552084582,
|
||||
"username": "laissaantuness",
|
||||
"role": "musicista",
|
||||
"display_name": "Lai",
|
||||
"channel_labels": [
|
||||
"Chitarra"
|
||||
],
|
||||
"mix_labels": [
|
||||
"Aux 7"
|
||||
],
|
||||
"setup_completed": true,
|
||||
"created_at": 1762199748.9965794,
|
||||
"created_at_str": "2025-11-03 19:55:48",
|
||||
"last_updated": 1762199837.8484952,
|
||||
"last_updated_str": "2025-11-03 19:57:17"
|
||||
}
|
||||
13
profiles/309700430.json
Normal file
13
profiles/309700430.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"user_id": 309700430,
|
||||
"username": "Cicciomt7",
|
||||
"role": "mixerista",
|
||||
"display_name": "Frank",
|
||||
"channel_labels": [],
|
||||
"mix_labels": [],
|
||||
"setup_completed": true,
|
||||
"created_at": 1762198859.8032317,
|
||||
"created_at_str": "2025-11-03 19:40:59",
|
||||
"last_updated": 1762198880.1237595,
|
||||
"last_updated_str": "2025-11-03 19:41:20"
|
||||
}
|
||||
17
profiles/392578198.json
Normal file
17
profiles/392578198.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"user_id": 392578198,
|
||||
"username": "Unknown",
|
||||
"role": "cantante",
|
||||
"display_name": "Rox",
|
||||
"channel_labels": [
|
||||
"Vox1"
|
||||
],
|
||||
"mix_labels": [
|
||||
"Aux2"
|
||||
],
|
||||
"setup_completed": true,
|
||||
"created_at": 1762199748.3117115,
|
||||
"created_at_str": "2025-11-03 19:55:48",
|
||||
"last_updated": 1762199831.8709695,
|
||||
"last_updated_str": "2025-11-03 19:57:11"
|
||||
}
|
||||
17
profiles/436176546.json
Normal file
17
profiles/436176546.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"user_id": 436176546,
|
||||
"username": "Gloriushhh",
|
||||
"role": "cantante",
|
||||
"display_name": "Gloriush",
|
||||
"channel_labels": [
|
||||
"VOX 2"
|
||||
],
|
||||
"mix_labels": [
|
||||
"AUX 3"
|
||||
],
|
||||
"setup_completed": true,
|
||||
"created_at": 1762199750.435028,
|
||||
"created_at_str": "2025-11-03 19:55:50",
|
||||
"last_updated": 1762199825.8800879,
|
||||
"last_updated_str": "2025-11-03 19:57:05"
|
||||
}
|
||||
@@ -3,11 +3,19 @@
|
||||
"username": "unnikked",
|
||||
"role": "mixerista",
|
||||
"display_name": "Nicola",
|
||||
"channels": [],
|
||||
"mixes": [],
|
||||
"channel_labels": [
|
||||
"I canali della batteria (kick",
|
||||
"drum",
|
||||
"pan",
|
||||
"tom",
|
||||
"etc)"
|
||||
],
|
||||
"mix_labels": [
|
||||
"Aux batteria"
|
||||
],
|
||||
"setup_completed": true,
|
||||
"created_at": 1762193898.87283,
|
||||
"created_at_str": "2025-11-03 19:18:18",
|
||||
"last_updated": 1762193916.4840386,
|
||||
"last_updated_str": "2025-11-03 19:18:36"
|
||||
"created_at": 1762198047.365003,
|
||||
"created_at_str": "2025-11-03 20:27:27",
|
||||
"last_updated": 1762799485.4542227,
|
||||
"last_updated_str": "2025-11-10 19:31:25"
|
||||
}
|
||||
17
profiles/540754896.json
Normal file
17
profiles/540754896.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"user_id": 540754896,
|
||||
"username": "merymoon001",
|
||||
"role": "musicista",
|
||||
"display_name": "Moon",
|
||||
"channel_labels": [
|
||||
"Tastiera"
|
||||
],
|
||||
"mix_labels": [
|
||||
"aux 8"
|
||||
],
|
||||
"setup_completed": true,
|
||||
"created_at": 1762199744.617781,
|
||||
"created_at_str": "2025-11-03 19:55:44",
|
||||
"last_updated": 1762199821.1137702,
|
||||
"last_updated_str": "2025-11-03 19:57:01"
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
dotenv
|
||||
google-genai
|
||||
pyyaml
|
||||
246
task_runner.py
Normal file
246
task_runner.py
Normal file
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
task_runner.py
|
||||
==============
|
||||
Esegue file di task YAML sul Yamaha TF5 tramite TF5MixerController.
|
||||
|
||||
Uso da CLI:
|
||||
python task_runner.py tasks_concerto_live.yaml # lista le cue disponibili
|
||||
python task_runner.py tasks_concerto_live.yaml CUE_01 # esegue una cue specifica
|
||||
python task_runner.py tasks_concerto_live.yaml --all # esegue tutte le cue in sequenza
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from mixer_controller import TF5MixerController
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# DISPATCHER: mappa action → metodo del controller
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
class TaskRunner:
|
||||
def __init__(self, controller: TF5MixerController):
|
||||
self.ctrl = controller
|
||||
|
||||
# Mappa nome azione → callable
|
||||
self._action_map = {
|
||||
"set_channel_level": self._set_channel_level,
|
||||
"set_channel_on_off": self._set_channel_on_off,
|
||||
"set_channel_pan": self._set_channel_pan,
|
||||
"mute_channels": self._mute_channels,
|
||||
"unmute_channels": self._unmute_channels,
|
||||
"set_mix_level": self._set_mix_level,
|
||||
"set_mix_on_off": self._set_mix_on_off,
|
||||
"set_channel_to_mix_level": self._set_channel_to_mix_level,
|
||||
"set_channel_to_mix_on_off":self._set_channel_to_mix_on_off,
|
||||
"recall_scene": self._recall_scene,
|
||||
"wait": self._wait,
|
||||
"refresh_cache": self._refresh_cache,
|
||||
}
|
||||
|
||||
# ── Handlers specifici ──────────────────────────────────
|
||||
|
||||
def _set_channel_level(self, step: dict) -> dict:
|
||||
return self.ctrl.set_channel_level(step["channel"], float(step["level_db"]))
|
||||
|
||||
def _set_channel_on_off(self, step: dict) -> dict:
|
||||
return self.ctrl.set_channel_on_off(step["channel"], bool(step["state"]))
|
||||
|
||||
def _set_channel_pan(self, step: dict) -> dict:
|
||||
return self.ctrl.set_channel_pan(step["channel"], int(step["pan"]))
|
||||
|
||||
def _mute_channels(self, step: dict) -> dict:
|
||||
return self.ctrl.mute_multiple_channels(list(step["channels"]))
|
||||
|
||||
def _unmute_channels(self, step: dict) -> dict:
|
||||
return self.ctrl.unmute_multiple_channels(list(step["channels"]))
|
||||
|
||||
def _set_mix_level(self, step: dict) -> dict:
|
||||
return self.ctrl.set_mix_level(step["mix"], float(step["level_db"]))
|
||||
|
||||
def _set_mix_on_off(self, step: dict) -> dict:
|
||||
return self.ctrl.set_mix_on_off(step["mix"], bool(step["state"]))
|
||||
|
||||
def _set_channel_to_mix_level(self, step: dict) -> dict:
|
||||
return self.ctrl.set_channel_to_mix_level(step["channel"], step["mix"], float(step["level_db"]))
|
||||
|
||||
def _set_channel_to_mix_on_off(self, step: dict) -> dict:
|
||||
return self.ctrl.set_channel_to_mix_on_off(step["channel"], step["mix"], bool(step["state"]))
|
||||
|
||||
def _recall_scene(self, step: dict) -> dict:
|
||||
return self.ctrl.recall_scene(str(step["bank"]), int(step["scene"]))
|
||||
|
||||
def _wait(self, step: dict) -> dict:
|
||||
ms = int(step.get("ms", 0))
|
||||
print(f" ⏱ Wait puro: {ms} ms")
|
||||
time.sleep(ms / 1000.0)
|
||||
return {"status": "success", "message": f"Atteso {ms} ms"}
|
||||
|
||||
def _refresh_cache(self, step: dict) -> dict:
|
||||
return self.ctrl.refresh_cache()
|
||||
|
||||
# ── Esecuzione di un singolo step ───────────────────────
|
||||
|
||||
def _run_step(self, step_num: int, step: dict) -> bool:
|
||||
"""Esegue un singolo step. Ritorna True se OK, False se errore."""
|
||||
action = step.get("action")
|
||||
delay_ms = int(step.get("delay_ms", 0))
|
||||
wait_ms = int(step.get("wait_ms", 0))
|
||||
|
||||
if delay_ms > 0:
|
||||
print(f" ⏳ delay_ms={delay_ms} prima dell'azione...")
|
||||
time.sleep(delay_ms / 1000.0)
|
||||
|
||||
handler = self._action_map.get(action)
|
||||
if handler is None:
|
||||
print(f" ⚠️ Step {step_num}: azione '{action}' non riconosciuta — saltato.")
|
||||
return False
|
||||
|
||||
print(f" ▶ Step {step_num}: {action} | params: { {k:v for k,v in step.items() if k not in ('action','delay_ms','wait_ms')} }")
|
||||
try:
|
||||
result = handler(step)
|
||||
status = result.get("status", "unknown")
|
||||
msg = result.get("message", "")
|
||||
icon = "✅" if status in ("success", "partial") else "❌"
|
||||
print(f" {icon} [{status}] {msg}")
|
||||
|
||||
if wait_ms > 0:
|
||||
time.sleep(wait_ms / 1000.0)
|
||||
|
||||
return status in ("success", "partial")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Eccezione durante '{action}': {e}")
|
||||
return False
|
||||
|
||||
# ── Esecuzione di una cue ────────────────────────────────
|
||||
|
||||
def run_cue(self, cue: dict) -> bool:
|
||||
"""
|
||||
Esegue tutti gli step di una cue.
|
||||
Ritorna True se tutti gli step sono andati a buon fine.
|
||||
"""
|
||||
cue_id = cue.get("id", "???")
|
||||
name = cue.get("name", "")
|
||||
on_error = cue.get("on_error", "stop")
|
||||
steps = cue.get("steps", [])
|
||||
|
||||
print(f"\n{'═'*60}")
|
||||
print(f"🎬 CUE: {cue_id} — {name}")
|
||||
if cue.get("description"):
|
||||
print(f" 📋 {cue['description']}")
|
||||
print(f" Steps: {len(steps)} | on_error: {on_error}")
|
||||
print(f"{'─'*60}")
|
||||
|
||||
all_ok = True
|
||||
for i, step in enumerate(steps, start=1):
|
||||
ok = self._run_step(i, step)
|
||||
if not ok:
|
||||
all_ok = False
|
||||
if on_error == "stop":
|
||||
print(f"\n 🛑 on_error=stop: cue {cue_id} interrotta allo step {i}.")
|
||||
return False
|
||||
# on_error == "continue": vai avanti
|
||||
|
||||
result_icon = "✅" if all_ok else "⚠️ (con errori)"
|
||||
print(f"\n {result_icon} Cue {cue_id} completata.")
|
||||
return all_ok
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# LOADER YAML
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
def load_task_file(path: str) -> dict:
|
||||
"""Carica e valida un file di task YAML."""
|
||||
p = Path(path)
|
||||
if not p.exists():
|
||||
raise FileNotFoundError(f"File non trovato: {path}")
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f)
|
||||
if "cues" not in data or not isinstance(data["cues"], list):
|
||||
raise ValueError("Il file YAML deve contenere una lista 'cues'.")
|
||||
return data
|
||||
|
||||
|
||||
def print_event_info(data: dict):
|
||||
event = data.get("event", {})
|
||||
if event:
|
||||
print(f"\n{'═'*60}")
|
||||
print(f"📅 Evento : {event.get('name', 'N/D')}")
|
||||
print(f" Data : {event.get('date', 'N/D')}")
|
||||
print(f" Venue : {event.get('venue', 'N/D')}")
|
||||
if event.get("notes"):
|
||||
print(f" Note : {event['notes']}")
|
||||
|
||||
|
||||
def list_cues(data: dict):
|
||||
print_event_info(data)
|
||||
print(f"\n{'═'*60}")
|
||||
print("📋 CUE DISPONIBILI:")
|
||||
print(f"{'─'*60}")
|
||||
for cue in data["cues"]:
|
||||
steps_count = len(cue.get("steps", []))
|
||||
print(f" {cue.get('id'):12s} │ {cue.get('name','')} ({steps_count} steps)")
|
||||
print(f"{'═'*60}\n")
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# ENTRY POINT
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
task_file = sys.argv[1]
|
||||
cue_arg = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
|
||||
try:
|
||||
data = load_task_file(task_file)
|
||||
except (FileNotFoundError, ValueError) as e:
|
||||
print(f"❌ Errore nel caricamento del file: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Solo lista cue senza mixer
|
||||
if cue_arg is None:
|
||||
list_cues(data)
|
||||
sys.exit(0)
|
||||
|
||||
# Connessione al mixer ed esecuzione
|
||||
print("🔌 Connessione al mixer...")
|
||||
import config
|
||||
|
||||
|
||||
with TF5MixerController(host=config.get_host()) as ctrl:
|
||||
runner = TaskRunner(ctrl)
|
||||
|
||||
if cue_arg == "--all":
|
||||
print_event_info(data)
|
||||
print("\n▶▶ Esecuzione di TUTTE le cue in sequenza...")
|
||||
for cue in data["cues"]:
|
||||
ok = runner.run_cue(cue)
|
||||
if not ok and cue.get("on_error", "stop") == "stop":
|
||||
print(f"\n🛑 Esecuzione interrotta a causa di un errore in {cue.get('id')}.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
# Cerca la cue per ID
|
||||
cue_found = next((c for c in data["cues"] if c.get("id") == cue_arg), None)
|
||||
if cue_found is None:
|
||||
print(f"❌ Cue '{cue_arg}' non trovata nel file.")
|
||||
list_cues(data)
|
||||
sys.exit(1)
|
||||
runner.run_cue(cue_found)
|
||||
|
||||
print("\n✅ Task runner completato.\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
71
task_schema.yaml
Normal file
71
task_schema.yaml
Normal file
@@ -0,0 +1,71 @@
|
||||
# ============================================================
|
||||
# SCHEMA DEI TASK - Yamaha TF5 Mixer Controller
|
||||
# ============================================================
|
||||
# Ogni file di task descrive UN evento (concerto, conferenza, ecc.)
|
||||
# composto da N cues, ognuna con N steps eseguiti in sequenza.
|
||||
#
|
||||
# STRUTTURA GENERALE:
|
||||
#
|
||||
# event: → metadati dell'evento (opzionale)
|
||||
# cues: → lista delle cue eseguibili
|
||||
# - id: → identificatore univoco (es. "CUE_01")
|
||||
# name: → nome leggibile
|
||||
# description: → note operative (opzionale)
|
||||
# on_error: → "stop" | "continue" (default: "stop")
|
||||
# steps: → lista di azioni in sequenza
|
||||
# - action: → tipo di azione (vedi sotto)
|
||||
# ...params → parametri specifici dell'azione
|
||||
# delay_ms: → attesa PRIMA di eseguire questo step (ms)
|
||||
# wait_ms: → attesa DOPO l'esecuzione (ms)
|
||||
#
|
||||
# ============================================================
|
||||
# TIPI DI AZIONE DISPONIBILI:
|
||||
# ============================================================
|
||||
#
|
||||
# 1. set_channel_level
|
||||
# channel: int (1-40)
|
||||
# level_db: float (es. 0.0, -10.5, -inf → usa -999)
|
||||
#
|
||||
# 2. set_channel_on_off
|
||||
# channel: int
|
||||
# state: bool (true = ON, false = OFF / mute)
|
||||
#
|
||||
# 3. set_channel_pan
|
||||
# channel: int
|
||||
# pan: int (-63 = sinistra, 0 = centro, +63 = destra)
|
||||
#
|
||||
# 4. mute_channels
|
||||
# channels: [int, int, ...]
|
||||
#
|
||||
# 5. unmute_channels
|
||||
# channels: [int, int, ...]
|
||||
#
|
||||
# 6. set_mix_level
|
||||
# mix: int (1-20)
|
||||
# level_db: float
|
||||
#
|
||||
# 7. set_mix_on_off
|
||||
# mix: int
|
||||
# state: bool
|
||||
#
|
||||
# 8. set_channel_to_mix_level
|
||||
# channel: int
|
||||
# mix: int
|
||||
# level_db: float
|
||||
#
|
||||
# 9. set_channel_to_mix_on_off
|
||||
# channel: int
|
||||
# mix: int
|
||||
# state: bool
|
||||
#
|
||||
# 10. recall_scene
|
||||
# bank: "a" | "b"
|
||||
# scene: int (0-99)
|
||||
#
|
||||
# 11. wait
|
||||
# ms: int → pausa pura senza azioni sul mixer
|
||||
#
|
||||
# 12. refresh_cache
|
||||
# (nessun parametro) → forza un aggiornamento completo della cache
|
||||
#
|
||||
# ============================================================
|
||||
238
tasks_concerto_live.yaml
Normal file
238
tasks_concerto_live.yaml
Normal file
@@ -0,0 +1,238 @@
|
||||
# ============================================================
|
||||
# TASK FILE - Concerto Live "Band Example"
|
||||
# Yamaha TF5 Mixer Controller
|
||||
# ============================================================
|
||||
|
||||
event:
|
||||
name: "Concerto Live - Band Example"
|
||||
date: "2025-06-14"
|
||||
venue: "Teatro Comunale"
|
||||
notes: "FOH setup. Mix 1=IEM Vocalist, Mix 2=IEM Chitarra, Mix 3=Wedge Batteria"
|
||||
|
||||
cues:
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# PRE-SHOW: tutto silenzioso, scene base caricata
|
||||
# ----------------------------------------------------------
|
||||
- id: "CUE_01"
|
||||
name: "Pre-Show - Carica scena base"
|
||||
description: "Richiama la scena di partenza e porta tutto a zero"
|
||||
on_error: "stop"
|
||||
steps:
|
||||
- action: recall_scene
|
||||
bank: "a"
|
||||
scene: 1
|
||||
|
||||
- action: wait
|
||||
ms: 3000 # attendi che il mixer elabori la scena
|
||||
|
||||
- action: refresh_cache
|
||||
wait_ms: 500
|
||||
|
||||
- action: mute_channels
|
||||
channels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
|
||||
- action: set_mix_on_off
|
||||
mix: 1
|
||||
state: false
|
||||
- action: set_mix_on_off
|
||||
mix: 2
|
||||
state: false
|
||||
- action: set_mix_on_off
|
||||
mix: 3
|
||||
state: false
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# SOUNDCHECK: apri i canali uno alla volta con fade-in
|
||||
# ----------------------------------------------------------
|
||||
- id: "CUE_02"
|
||||
name: "Soundcheck - Fade-in canali batteria"
|
||||
description: "Canali 1-4 (kick, snare, OH sx, OH dx) portati a -10 dB gradualmente"
|
||||
on_error: "continue"
|
||||
steps:
|
||||
# Kick
|
||||
- action: set_channel_level
|
||||
channel: 1
|
||||
level_db: -40.0
|
||||
- action: set_channel_on_off
|
||||
channel: 1
|
||||
state: true
|
||||
wait_ms: 200
|
||||
- action: set_channel_level
|
||||
channel: 1
|
||||
level_db: -20.0
|
||||
wait_ms: 200
|
||||
- action: set_channel_level
|
||||
channel: 1
|
||||
level_db: -10.0
|
||||
wait_ms: 300
|
||||
|
||||
# Snare
|
||||
- action: set_channel_level
|
||||
channel: 2
|
||||
level_db: -40.0
|
||||
delay_ms: 500
|
||||
- action: set_channel_on_off
|
||||
channel: 2
|
||||
state: true
|
||||
wait_ms: 200
|
||||
- action: set_channel_level
|
||||
channel: 2
|
||||
level_db: -10.0
|
||||
wait_ms: 300
|
||||
|
||||
# Overhead Sx + Dx insieme
|
||||
- action: set_channel_level
|
||||
channel: 3
|
||||
level_db: -40.0
|
||||
delay_ms: 500
|
||||
- action: set_channel_level
|
||||
channel: 4
|
||||
level_db: -40.0
|
||||
- action: set_channel_on_off
|
||||
channel: 3
|
||||
state: true
|
||||
- action: set_channel_on_off
|
||||
channel: 4
|
||||
state: true
|
||||
wait_ms: 200
|
||||
- action: set_channel_pan
|
||||
channel: 3
|
||||
pan: -30 # OH sx → sinistra
|
||||
- action: set_channel_pan
|
||||
channel: 4
|
||||
pan: 30 # OH dx → destra
|
||||
wait_ms: 200
|
||||
- action: set_channel_level
|
||||
channel: 3
|
||||
level_db: -14.0
|
||||
- action: set_channel_level
|
||||
channel: 4
|
||||
level_db: -14.0
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# IEM MIX 1 (Vocalist): setup invii
|
||||
# ----------------------------------------------------------
|
||||
- id: "CUE_03"
|
||||
name: "IEM Mix 1 - Setup invii vocalist"
|
||||
description: "Configura il mix 1 per il vocalist: voce principale alta, resto basso"
|
||||
on_error: "continue"
|
||||
steps:
|
||||
- action: set_mix_level
|
||||
mix: 1
|
||||
level_db: 0.0
|
||||
- action: set_mix_on_off
|
||||
mix: 1
|
||||
state: true
|
||||
wait_ms: 200
|
||||
|
||||
# Voce principale (ch 9) → Mix 1 alta
|
||||
- action: set_channel_to_mix_level
|
||||
channel: 9
|
||||
mix: 1
|
||||
level_db: 0.0
|
||||
- action: set_channel_to_mix_on_off
|
||||
channel: 9
|
||||
mix: 1
|
||||
state: true
|
||||
|
||||
# Chitarra ritmica (ch 5) → Mix 1 media
|
||||
- action: set_channel_to_mix_level
|
||||
channel: 5
|
||||
mix: 1
|
||||
level_db: -6.0
|
||||
delay_ms: 100
|
||||
- action: set_channel_to_mix_on_off
|
||||
channel: 5
|
||||
mix: 1
|
||||
state: true
|
||||
|
||||
# Kick (ch 1) → Mix 1 bassa (solo click per tempo)
|
||||
- action: set_channel_to_mix_level
|
||||
channel: 1
|
||||
mix: 1
|
||||
level_db: -12.0
|
||||
delay_ms: 100
|
||||
- action: set_channel_to_mix_on_off
|
||||
channel: 1
|
||||
mix: 1
|
||||
state: true
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# SHOW START: tutto ON, master a 0 dB
|
||||
# ----------------------------------------------------------
|
||||
- id: "CUE_04"
|
||||
name: "SHOW START - Apri tutto"
|
||||
description: "Porta tutti i canali attivi e il master a nominal"
|
||||
on_error: "stop"
|
||||
steps:
|
||||
- action: unmute_channels
|
||||
channels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
|
||||
- action: set_mix_on_off
|
||||
mix: 1
|
||||
state: true
|
||||
- action: set_mix_on_off
|
||||
mix: 2
|
||||
state: true
|
||||
- action: set_mix_on_off
|
||||
mix: 3
|
||||
state: true
|
||||
|
||||
- action: recall_scene
|
||||
bank: "a"
|
||||
scene: 10 # scena "SHOW" pre-salvata
|
||||
wait_ms: 2000
|
||||
|
||||
- action: refresh_cache
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# PAUSA: muta rapidamente tutto il palco
|
||||
# ----------------------------------------------------------
|
||||
- id: "CUE_05"
|
||||
name: "PAUSA - Muta palco"
|
||||
description: "Muta tutti i canali di palco, lascia accesa la diffusione BG music"
|
||||
on_error: "continue"
|
||||
steps:
|
||||
- action: mute_channels
|
||||
channels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
|
||||
- action: set_channel_on_off # ch 16 = BG music playback
|
||||
channel: 16
|
||||
state: true
|
||||
delay_ms: 200
|
||||
- action: set_channel_level
|
||||
channel: 16
|
||||
level_db: -6.0
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# FINE SHOW: fade-out master in 5 step da 1 secondo
|
||||
# ----------------------------------------------------------
|
||||
- id: "CUE_06"
|
||||
name: "FINE SHOW - Fade out generale"
|
||||
description: "Abbassa tutti i canali gradualmente in 5 secondi"
|
||||
on_error: "continue"
|
||||
steps:
|
||||
- action: set_channel_level
|
||||
channel: 9
|
||||
level_db: -6.0
|
||||
wait_ms: 1000
|
||||
- action: set_channel_level
|
||||
channel: 9
|
||||
level_db: -12.0
|
||||
wait_ms: 1000
|
||||
- action: set_channel_level
|
||||
channel: 9
|
||||
level_db: -20.0
|
||||
wait_ms: 1000
|
||||
- action: set_channel_level
|
||||
channel: 9
|
||||
level_db: -40.0
|
||||
wait_ms: 1000
|
||||
- action: mute_channels
|
||||
channels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 16]
|
||||
|
||||
- action: recall_scene
|
||||
bank: "a"
|
||||
scene: 0 # scena vuota / safe
|
||||
delay_ms: 500
|
||||
287
telegram_bot.py
287
telegram_bot.py
@@ -63,8 +63,8 @@ class UserProfile:
|
||||
self.username = username
|
||||
self.role: Optional[str] = None
|
||||
self.display_name: Optional[str] = None
|
||||
self.channels: List[int] = [] # Canali assegnati
|
||||
self.mixes: List[int] = [] # Mix/aux assegnati
|
||||
self.channel_labels: List[str] = [] # Etichette dei canali assegnati
|
||||
self.mix_labels: List[str] = [] # Etichette dei mix/aux assegnati
|
||||
self.setup_completed = False
|
||||
self.created_at = time.time()
|
||||
self.last_updated = time.time()
|
||||
@@ -76,8 +76,8 @@ class UserProfile:
|
||||
"username": self.username,
|
||||
"role": self.role,
|
||||
"display_name": self.display_name,
|
||||
"channels": self.channels,
|
||||
"mixes": self.mixes,
|
||||
"channel_labels": self.channel_labels,
|
||||
"mix_labels": self.mix_labels,
|
||||
"setup_completed": self.setup_completed,
|
||||
"created_at": self.created_at,
|
||||
"created_at_str": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.created_at)),
|
||||
@@ -91,8 +91,8 @@ class UserProfile:
|
||||
profile = cls(data["user_id"], data["username"])
|
||||
profile.role = data.get("role")
|
||||
profile.display_name = data.get("display_name")
|
||||
profile.channels = data.get("channels", [])
|
||||
profile.mixes = data.get("mixes", [])
|
||||
profile.channel_labels = data.get("channel_labels", [])
|
||||
profile.mix_labels = data.get("mix_labels", [])
|
||||
profile.setup_completed = data.get("setup_completed", False)
|
||||
profile.created_at = data.get("created_at", time.time())
|
||||
profile.last_updated = data.get("last_updated", time.time())
|
||||
@@ -102,36 +102,6 @@ class UserProfile:
|
||||
"""Restituisce le info sul ruolo."""
|
||||
return self.ROLES.get(self.role, {})
|
||||
|
||||
def can_access_channel(self, channel: int) -> bool:
|
||||
"""Verifica se l'utente può accedere a un canale."""
|
||||
if not self.role:
|
||||
return False
|
||||
|
||||
permissions = self.get_role_info().get("permissions", [])
|
||||
|
||||
if "all" in permissions:
|
||||
return True
|
||||
|
||||
if "own_channel" in permissions:
|
||||
return channel in self.channels
|
||||
|
||||
return False
|
||||
|
||||
def can_access_mix(self, mix: int) -> bool:
|
||||
"""Verifica se l'utente può accedere a un mix."""
|
||||
if not self.role:
|
||||
return False
|
||||
|
||||
permissions = self.get_role_info().get("permissions", [])
|
||||
|
||||
if "all" in permissions:
|
||||
return True
|
||||
|
||||
if "own_mix" in permissions:
|
||||
return mix in self.mixes
|
||||
|
||||
return False
|
||||
|
||||
def get_summary(self) -> str:
|
||||
"""Restituisce un riepilogo del profilo."""
|
||||
if not self.setup_completed:
|
||||
@@ -140,15 +110,14 @@ class UserProfile:
|
||||
role_info = self.get_role_info()
|
||||
msg = f"{role_info['emoji']} **{self.display_name}** ({role_info['name']})\n\n"
|
||||
|
||||
if self.channels:
|
||||
msg += f"🎚️ Canali: {', '.join(map(str, self.channels))}\n"
|
||||
if self.channel_labels:
|
||||
msg += f"🎚️ Canali: {', '.join(self.channel_labels)}\n"
|
||||
|
||||
if self.mixes:
|
||||
msg += f"🔊 Mix: {', '.join(map(str, self.mixes))}\n"
|
||||
if self.mix_labels:
|
||||
msg += f"🔊 Mix: {', '.join(self.mix_labels)}\n"
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
class ProfileManager:
|
||||
"""Gestisce i profili utente."""
|
||||
|
||||
@@ -221,7 +190,6 @@ class ProfileManager:
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
class ConversationManager:
|
||||
"""Gestisce il salvataggio delle conversazioni su file JSON."""
|
||||
|
||||
@@ -289,7 +257,6 @@ class ConversationManager:
|
||||
print(f"❌ Errore nell'eliminazione conversazione {user_id}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
class TelegramBot:
|
||||
"""Bot Telegram base con long polling."""
|
||||
|
||||
@@ -423,6 +390,7 @@ class TF5TelegramBot:
|
||||
self.controller.set_channel_to_mix_level,
|
||||
self.controller.set_channel_to_mix_on_off,
|
||||
self.controller.get_channel_to_mix_info,
|
||||
self.controller.get_full_mix_details,
|
||||
],
|
||||
temperature=0,
|
||||
)
|
||||
@@ -504,6 +472,37 @@ GRUPPI DI CANALI:
|
||||
- "spegni tutto tranne..." → muta tutti gli altri
|
||||
- "solo i microfoni" → attiva solo quelli, spegni il resto
|
||||
|
||||
---
|
||||
ANALISI DEL MIX (per utenti esperti)
|
||||
Quando un utente chiede di "analizzare un mix", "controllare i volumi della diretta", o "dare suggerimenti per il mix X", devi usare la funzione `get_full_mix_details(mix_number)`.
|
||||
Questa funzione ti darà la lista di tutti i canali inviati a quel mix, con i loro livelli.
|
||||
|
||||
Il tuo compito è analizzare questi dati e fornire suggerimenti pratici per migliorare l'equilibrio, specialmente per un mix destinato a una diretta streaming o registrazione.
|
||||
|
||||
Linee guida per l'analisi:
|
||||
1. **Gerarchia dei Volumi**:
|
||||
- **Predicatore/Oratore Principale**: Deve essere il canale più alto di tutti. Cerca nomi come "Predicatore", "Pastore", "Pulpito". Il suo livello deve essere il punto di riferimento.
|
||||
- **Voce Solista (Lead Vocal)**: Subito dopo il predicatore. Deve essere chiaramente sopra gli strumenti e i cori. Cerca "Voce L", "Cantante P".
|
||||
- **Cori/Voci Secondarie**: Dovrebbero essere a un livello inferiore rispetto alla voce solista, per supportarla senza coprirla.
|
||||
- **Strumenti Melodici**: (Tastiere, Chitarre) Dovrebbero creare un "tappeto sonoro" sotto le voci. Il loro livello deve essere bilanciato per non competere con le voci.
|
||||
- **Sezione Ritmica**: (Basso, Batteria) Devono essere presenti ma controllati. Il basso deve essere udibile ma non rimbombante, la batteria presente ma non invadente.
|
||||
|
||||
2. **Problemi Comuni da Segnalare**:
|
||||
- **Canali Spenti ma Inviati**: Controlla il campo `channel_is_on`. Se è `False` per un canale con un livello di send significativo, è un errore comune. Segnalalo dicendo: "Attenzione, il canale X ('Nome Canale') è spento sul suo fader principale, ma stai cercando di mandarlo al mix. Non si sentirà nulla. Devi prima accenderlo."
|
||||
- **Livelli Estremi**: Segnala i canali con send molto alti (sopra +5 dB, rischio di distorsione) o molto bassi (sotto -30 dB, forse inutili o dimenticati).
|
||||
- **Equilibrio Voci/Strumenti**: Se il livello medio degli strumenti è uguale o superiore a quello della voce solista, suggerisci di abbassare gli strumenti per fare spazio alla voce. Esempio: "Le tastiere sono a -5dB e la voce a -6dB. Prova ad abbassare un po' le tastiere per far emergere meglio la voce."
|
||||
|
||||
3. **Formato della Risposta**:
|
||||
- Inizia con un riepilogo generale del mix (es: "Ecco un'analisi del Mix 15 (Diretta Streaming):").
|
||||
- Usa sezioni chiare con emoji per una lettura rapida:
|
||||
- `✅ Punti di Forza`: Se vedi un buon equilibrio di base.
|
||||
- `⚠️ Suggerimenti per Migliorare`: Elenco puntato con azioni concrete.
|
||||
- `🤔 Problemi Rilevati`: Per i canali spenti o livelli anomali.
|
||||
- Sii propositivo e offri soluzioni, non solo problemi.
|
||||
|
||||
Esempio di analisi: "Ho analizzato il mix 15. La voce del predicatore è correttamente il canale più forte. Tuttavia, la chitarra acustica è quasi allo stesso livello della voce solista. Ti suggerisco di abbassare la chitarra di circa 3dB per dare più risalto al cantante. Ho notato anche che il canale 12 ('Coro Dx') è spento, quindi non sta contribuendo al mix. Vuoi che lo accenda?"
|
||||
---
|
||||
|
||||
CASI PARTICOLARI:
|
||||
- Se la richiesta è ambigua, chiedi chiarimenti in modo colloquiale
|
||||
- Se serve cercare un canale/mix, usa prima la cache (search_channels_by_name / search_mixes_by_name)
|
||||
@@ -536,13 +535,17 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
||||
instruction += f"Nome: {profile.display_name}\n"
|
||||
instruction += f"Ruolo: {role_info['name']} {role_info['emoji']}\n"
|
||||
|
||||
if profile.channels:
|
||||
instruction += f"\nCanali assegnati: {', '.join(map(str, profile.channels))}\n"
|
||||
instruction += "Quando l'utente dice 'il mio canale' o 'il mio mic/strumento', si riferisce a questi.\n"
|
||||
if profile.channel_labels:
|
||||
labels_str = ', '.join([f"'{l}'" for l in profile.channel_labels])
|
||||
instruction += f"\nNomi dei canali assegnati: {labels_str}\n"
|
||||
instruction += "Quando l'utente dice 'il mio canale' o 'il mio mic/strumento', si riferisce a questi. "
|
||||
instruction += "**DEVI usare `search_channels_by_name` con questi nomi per trovare il numero di canale corretto prima di eseguire qualsiasi altra azione.**\n"
|
||||
|
||||
if profile.mixes:
|
||||
instruction += f"\nMix assegnati: {', '.join(map(str, profile.mixes))}\n"
|
||||
instruction += "Quando l'utente dice 'le mie cuffie', 'il mio monitor', 'in cuffia', si riferisce a questi mix.\n"
|
||||
if profile.mix_labels:
|
||||
labels_str = ', '.join([f"'{l}'" for l in profile.mix_labels])
|
||||
instruction += f"\nNomi dei mix assegnati: {labels_str}\n"
|
||||
instruction += "Quando l'utente dice 'le mie cuffie', 'il mio monitor', 'in cuffia', si riferisce a questi mix. "
|
||||
instruction += "**DEVI usare `search_mixes_by_name` con questi nomi per trovare il numero di mix corretto prima di eseguire qualsiasi altra azione.**\n"
|
||||
|
||||
# Istruzioni specifiche per ruolo
|
||||
permissions = role_info.get("permissions", [])
|
||||
@@ -555,77 +558,27 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
||||
else:
|
||||
instruction += f"\n⚠️ LIMITAZIONI DI ACCESSO ({role_info['name']}):\n"
|
||||
|
||||
if "own_channel" in permissions and profile.channels:
|
||||
instruction += f"- Canali controllabili: SOLO {', '.join(map(str, profile.channels))}\n"
|
||||
if "own_channel" in permissions and profile.channel_labels:
|
||||
instruction += f"- Canali controllabili: SOLO quelli che corrispondono ai nomi nel tuo profilo.\n"
|
||||
|
||||
if "own_mix" in permissions and profile.mixes:
|
||||
instruction += f"- Mix controllabili: SOLO {', '.join(map(str, profile.mixes))}\n"
|
||||
instruction += f"- Puoi gestire i send di QUALSIASI canale verso i TUOI mix ({', '.join(map(str, profile.mixes))})\n"
|
||||
if "own_mix" in permissions and profile.mix_labels:
|
||||
instruction += f"- Mix controllabili: SOLO quelli che corrispondono ai nomi nel tuo profilo.\n"
|
||||
instruction += f"- Puoi gestire i send di QUALSIASI canale verso i TUOI mix.\n"
|
||||
|
||||
instruction += "\n❌ NON puoi:\n"
|
||||
if "own_channel" not in permissions:
|
||||
instruction += "- Modificare il volume/stato dei canali\n"
|
||||
if "own_mix" not in permissions:
|
||||
instruction += "- Modificare il volume/stato dei mix\n"
|
||||
instruction += "- Caricare scene (solo i mixeristi)\n"
|
||||
instruction += "- Accedere a canali/mix non assegnati\n"
|
||||
instruction += "- Caricare scene (solo i mixeristi).\n"
|
||||
instruction += "- Accedere a canali/mix non associati al tuo profilo.\n"
|
||||
|
||||
instruction += "\n💡 INTERPRETAZIONE RICHIESTE:\n"
|
||||
instruction += "- 'alzami la chitarra' = alza il send della chitarra nel TUO mix\n"
|
||||
instruction += "- 'più voce' = alza il send della voce nel TUO mix\n"
|
||||
instruction += "- 'meno batteria in cuffia' = abbassa il send della batteria nel TUO mix\n"
|
||||
instruction += "- Se l'utente chiede di fare qualcosa fuori dai suoi permessi, spiega gentilmente che non può e suggerisci di chiedere al mixerista\n"
|
||||
instruction += "- 'alzami la chitarra' = alza il send della chitarra nel TUO mix (identificato dal suo nome).\n"
|
||||
instruction += "- 'più voce' = alza il send della voce nel TUO mix.\n"
|
||||
instruction += "- 'meno batteria in cuffia' = abbassa il send della batteria nel TUO mix.\n"
|
||||
instruction += "- Se l'utente chiede di fare qualcosa fuori dai suoi permessi, spiega gentilmente che non può e suggerisci di chiedere al mixerista.\n"
|
||||
|
||||
instruction += f"\n{'='*70}\n"
|
||||
|
||||
return instruction
|
||||
|
||||
def _check_permission(self, profile: UserProfile, operation: str,
|
||||
channel: Optional[int] = None,
|
||||
mix: Optional[int] = None) -> tuple[bool, str]:
|
||||
"""
|
||||
Verifica se l'utente ha i permessi per un'operazione.
|
||||
Ritorna (permitted, error_message)
|
||||
"""
|
||||
if not profile.setup_completed:
|
||||
return False, "⚠️ Devi prima configurare il tuo profilo con /setup"
|
||||
|
||||
role_info = profile.get_role_info()
|
||||
permissions = role_info.get("permissions", [])
|
||||
|
||||
# Mixeristi possono fare tutto
|
||||
if "all" in permissions:
|
||||
return True, ""
|
||||
|
||||
# Verifica permessi specifici
|
||||
if operation in ["set_channel_level", "set_channel_on_off", "set_channel_pan",
|
||||
"mute_multiple_channels", "unmute_multiple_channels"]:
|
||||
if "own_channel" not in permissions:
|
||||
return False, "❌ Solo i mixeristi possono controllare i canali direttamente"
|
||||
|
||||
if channel and not profile.can_access_channel(channel):
|
||||
return False, f"❌ Non hai accesso al canale {channel}. I tuoi canali: {', '.join(map(str, profile.channels))}"
|
||||
|
||||
if operation in ["set_mix_level", "set_mix_on_off"]:
|
||||
if "own_mix" not in permissions:
|
||||
return False, "❌ Non hai permessi per controllare i mix"
|
||||
|
||||
if mix and not profile.can_access_mix(mix):
|
||||
return False, f"❌ Non hai accesso al mix {mix}. I tuoi mix: {', '.join(map(str, profile.mixes))}"
|
||||
|
||||
if operation == "recall_scene":
|
||||
return False, "❌ Solo i mixeristi possono caricare scene"
|
||||
|
||||
# Le operazioni di send ai mix sono permesse se hai accesso al mix di destinazione
|
||||
if operation in ["set_channel_to_mix_level", "set_channel_to_mix_on_off"]:
|
||||
if "own_mix" not in permissions:
|
||||
return False, "❌ Non hai permessi per controllare i send ai mix"
|
||||
|
||||
if mix and not profile.can_access_mix(mix):
|
||||
return False, f"❌ Non hai accesso al mix {mix}. I tuoi mix: {', '.join(map(str, profile.mixes))}"
|
||||
|
||||
return True, ""
|
||||
|
||||
def get_profile(self, user_id: int, username: str) -> UserProfile:
|
||||
"""Ottiene il profilo di un utente."""
|
||||
if user_id not in self.profiles:
|
||||
@@ -694,7 +647,6 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
||||
role_info = profile.get_role_info()
|
||||
permissions = role_info.get("permissions", [])
|
||||
|
||||
# Se è mixerista, ha finito
|
||||
if "all" in permissions:
|
||||
profile.setup_completed = True
|
||||
self.profile_manager.save_profile(profile)
|
||||
@@ -708,59 +660,47 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
||||
f"• 'Richiama la scena A10'\n"
|
||||
f"• 'Mostrami tutti i canali'")
|
||||
|
||||
# Altrimenti, chiedi i canali (se necessario)
|
||||
if "own_channel" in permissions:
|
||||
self.bot.send_message(
|
||||
user_id,
|
||||
f"🎚️ Quali canali usi?\n\n"
|
||||
f"Esempi:\n"
|
||||
f"• Un canale: 5\n"
|
||||
f"• Più canali: 5, 7, 12\n"
|
||||
f"• Range: 5-8 (canali 5, 6, 7, 8)\n"
|
||||
f"• Misti: 3, 5-7, 10\n\n"
|
||||
f"(Scrivi 0 se non usi canali diretti)"
|
||||
f"🎚️ **Come si chiama il tuo canale/strumento?**\n\n"
|
||||
f"Scrivi il nome esatto che vedi sull'etichetta del mixer (es: 'VOX Marco', 'CH Acustica').\n"
|
||||
f"Se hai più canali, separali con una virgola (es: 'Tastiera L', 'Tastiera R').\n\n"
|
||||
f"(Scrivi 'nessuno' se non hai un canale specifico)"
|
||||
)
|
||||
self.setup_states[user_id] = {"step": "channels", "role": profile.role}
|
||||
return ""
|
||||
else:
|
||||
# Salta direttamente ai mix
|
||||
self.setup_states[user_id] = {"step": "channels"}
|
||||
elif "own_mix" in permissions:
|
||||
self.bot.send_message(
|
||||
user_id,
|
||||
f"🔊 Quali mix/aux usi per le tue cuffie o uscita?\n\n"
|
||||
f"Esempi:\n"
|
||||
f"• Un mix: 3\n"
|
||||
f"• Più mix: 3, 5\n"
|
||||
f"• Range: 3-5\n"
|
||||
f"• Mix stereo: 9, 10 (in-ear stereo)\n\n"
|
||||
f"(Scrivi 0 se non usi mix specifici)"
|
||||
f"🔊 **Come si chiama il tuo mix per le cuffie/monitor?**\n\n"
|
||||
f"Scrivi il nome esatto (es: 'InEar Marco', 'Cuffia Palco Sx').\n"
|
||||
f"Se usi un mix stereo, scrivi entrambi i nomi separati da virgola (es: 'IEM L', 'IEM R').\n\n"
|
||||
f"(Scrivi 'nessuno' se non usi un mix specifico)"
|
||||
)
|
||||
self.setup_states[user_id] = {"step": "mixes", "role": profile.role}
|
||||
return ""
|
||||
self.setup_states[user_id] = {"step": "mixes"}
|
||||
|
||||
# Step: inserimento canali
|
||||
return ""
|
||||
|
||||
# Step: inserimento etichette canali
|
||||
elif step == "channels":
|
||||
channels = self._parse_channel_list(text)
|
||||
profile.channels = channels
|
||||
labels_text = text.strip()
|
||||
if labels_text.lower() != 'nessuno':
|
||||
profile.channel_labels = [label.strip() for label in labels_text.split(',')]
|
||||
|
||||
role_info = profile.get_role_info()
|
||||
permissions = role_info.get("permissions", [])
|
||||
|
||||
# Chiedi i mix se necessario
|
||||
if "own_mix" in permissions:
|
||||
self.bot.send_message(
|
||||
user_id,
|
||||
f"🔊 Quali mix/aux usi per le tue cuffie o uscita?\n\n"
|
||||
f"Esempi:\n"
|
||||
f"• Un mix: 3\n"
|
||||
f"• Più mix: 3, 5\n"
|
||||
f"• Range: 3-5\n"
|
||||
f"• Mix stereo: 9, 10 (in-ear stereo)\n\n"
|
||||
f"(Scrivi 0 se non usi mix specifici)"
|
||||
f"🔊 **Come si chiama il tuo mix per le cuffie/monitor?**\n\n"
|
||||
f"Scrivi il nome esatto (es: 'InEar Marco', 'Cuffia Palco Sx').\n"
|
||||
f"Se usi un mix stereo, scrivi entrambi i nomi separati da virgola (es: 'IEM L', 'IEM R').\n\n"
|
||||
f"(Scrivi 'nessuno' se non usi un mix specifico)"
|
||||
)
|
||||
self.setup_states[user_id] = {"step": "mixes", "role": profile.role}
|
||||
self.setup_states[user_id] = {"step": "mixes"}
|
||||
return ""
|
||||
else:
|
||||
# Finito
|
||||
profile.setup_completed = True
|
||||
self.profile_manager.save_profile(profile)
|
||||
del self.setup_states[user_id]
|
||||
@@ -771,59 +711,32 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
||||
f"• 'Alza il mio canale'\n"
|
||||
f"• 'Spegni il mio mic'")
|
||||
|
||||
# Step: inserimento mix
|
||||
# Step: inserimento etichette mix
|
||||
elif step == "mixes":
|
||||
mixes = self._parse_channel_list(text) # Stessa logica per i mix
|
||||
profile.mixes = mixes
|
||||
labels_text = text.strip()
|
||||
if labels_text.lower() != 'nessuno':
|
||||
profile.mix_labels = [label.strip() for label in labels_text.split(',')]
|
||||
|
||||
profile.setup_completed = True
|
||||
self.profile_manager.save_profile(profile)
|
||||
del self.setup_states[user_id]
|
||||
|
||||
role_info = profile.get_role_info()
|
||||
|
||||
example_commands = []
|
||||
if profile.channels:
|
||||
example_commands.append("• 'Alza il mio canale'")
|
||||
if profile.mixes:
|
||||
if profile.channel_labels:
|
||||
example_commands.append("• 'Alza il volume del mio canale'")
|
||||
if profile.mix_labels:
|
||||
example_commands.extend([
|
||||
"• 'Alzami la chitarra' (nel tuo mix)",
|
||||
"• 'Alzami la chitarra nel mio monitor'",
|
||||
"• 'Più voce in cuffia'",
|
||||
"• 'Meno batteria'"
|
||||
])
|
||||
|
||||
return (f"✅ Configurazione completata!\n\n"
|
||||
f"{profile.get_summary()}\n"
|
||||
f"Esempi di comandi:\n" + "\n".join(example_commands))
|
||||
f"{profile.get_summary()}\n\n"
|
||||
f"Esempi di comandi che puoi usare:\n" + "\n".join(example_commands))
|
||||
|
||||
return ""
|
||||
|
||||
def _parse_channel_list(self, text: str) -> List[int]:
|
||||
"""Parse una lista di canali/mix dal testo utente."""
|
||||
channels = []
|
||||
|
||||
# Rimuovi spazi e split per virgola
|
||||
parts = [p.strip() for p in text.split(',')]
|
||||
|
||||
for part in parts:
|
||||
# Range (es: 5-8)
|
||||
if '-' in part:
|
||||
try:
|
||||
start, end = map(int, part.split('-'))
|
||||
channels.extend(range(start, end + 1))
|
||||
except:
|
||||
pass
|
||||
# Singolo numero
|
||||
else:
|
||||
try:
|
||||
num = int(part)
|
||||
if num > 0: # Ignora 0
|
||||
channels.append(num)
|
||||
except:
|
||||
pass
|
||||
|
||||
return sorted(list(set(channels))) # Rimuovi duplicati e ordina
|
||||
|
||||
def _get_file_path(self, file_id: str) -> str:
|
||||
"""Richiede a Telegram il percorso di un file."""
|
||||
result = self.bot._make_request("getFile", {"file_id": file_id})
|
||||
@@ -971,16 +884,16 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
||||
msg += "• 'Alza il canale 5 a -10 dB'\n"
|
||||
msg += "• 'Spegni i canali dal 1 al 5'\n"
|
||||
msg += "• 'Richiama la scena A10'\n"
|
||||
msg += "• 'Mostrami lo stato del canale 12'\n"
|
||||
msg += "• 'Analizza il mix 15 per la diretta'\n"
|
||||
else:
|
||||
msg += "**Esempi di comandi:**\n"
|
||||
if profile.channels:
|
||||
if profile.channel_labels:
|
||||
msg += "• 'Alza il mio canale'\n"
|
||||
msg += "• 'Spegni il mio mic'\n"
|
||||
if profile.mixes:
|
||||
if profile.mix_labels:
|
||||
msg += "• 'Alzami la chitarra' (nel tuo mix)\n"
|
||||
msg += "• 'Più voce in cuffia'\n"
|
||||
msg += "• 'Meno batteria'\n"
|
||||
msg += "• 'Analizza il mio mix per la diretta'\n"
|
||||
|
||||
msg += "\n**Comandi speciali:**\n"
|
||||
msg += "/help - Mostra questo messaggio\n"
|
||||
@@ -1170,7 +1083,7 @@ def main():
|
||||
try:
|
||||
bot = TF5TelegramBot(
|
||||
telegram_token=telegram_token,
|
||||
mixer_host=args.mixer_host,
|
||||
mixer_host=get_host(),
|
||||
mixer_port=args.mixer_port,
|
||||
conversations_dir=args.conversations_dir,
|
||||
profiles_dir=args.profiles_dir
|
||||
|
||||
Reference in New Issue
Block a user