- Go 79.3%
- HTML 12.9%
- Python 5.8%
- Shell 1.6%
- Dockerfile 0.4%
| .claude | ||
| docs | ||
| internal | ||
| scripts | ||
| state | ||
| static | ||
| .gitignore | ||
| blog.md | ||
| byschbuster | ||
| deploy_raspberry.sh | ||
| docker-compose.yml | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| localbuster | ||
| main.go | ||
| README.md | ||
byschbuster
Server locale che gestisce VLC e mantiene un catalogo dei file video hostati su domini controllati. Espone un'interfaccia web per cercare contenuti e avviare la riproduzione; supporta connessioni sia dalla rete locale che da internet (VLC gira sempre in locale, il client può connettersi da qualunque posto).
Funzionalità principali
- Catalogo — visita ricorsivamente le cartelle radice sul dominio raggiungibile e costruisce una lista piatta di tutti i file presenti.
- Arricchimento AI — interroga Gemini con i metadati del catalogo per produrre titolo, serie, stagione, episodio, anno e trame.
- Scan traccia italiana — usa
ffprobesulladirect_urlper rilevare l'indice della traccia audio italiana. - Indicizzazione e ricerca — costruisce un indice in memoria da SQLite + catalogo; ricerca fuzzy con
closestmatch.
La rete viene tentata prima sul dominio locale (granarolo.local:3923), con fallback su internet (dropbox.byschii.com).
Per i dettagli operativi vedi docs/background_ops.md e docs/user_interaction.md.
Prerequisiti
- Go 1.23+
- VLC installato e raggiungibile nel PATH
- Una chiave API Gemini (per l'arricchimento AI)
Installazione
go mod tidy
go build -o byschbuster .
File .env
Le variabili possono essere messe in un file .env nella root del progetto. Le variabili già presenti nell'ambiente hanno sempre precedenza.
GEMINI_API_KEY=la-tua-chiave
GEMINI_MODEL=gemini-3.1-flash-lite-preview
PORT=8080
STATE_DIR=./state
CONNECTION_CHECK_INTERVAL_SECS=60
LUMIERE_CHECK_INTERVAL_SECS=60
CATALOG_INTERVAL_SECS=900
ENRICHMENT_INTERVAL_SECS=1200
ITA_TRACK_SCAN_INTERVAL_SECS=60
INDEX_INTERVAL_SECS=1500
LOCAL_DOMAIN=granarolo.local:3923
INTERNET_DOMAIN=dropbox.byschii.com
LUMIERE_URL=http://lumiere.local:8888
ROOT_FOLDERS=4. cartoni, 6. film, 7. serie, 8. tvshow
Variabili d'ambiente
| Variabile | Obbligatoria | Default | Descrizione |
|---|---|---|---|
GEMINI_API_KEY |
no* | — | Chiave API Google Gemini. Senza di essa l'arricchimento AI è disabilitato |
GEMINI_MODEL |
no | gemini-3.1-flash-lite-preview |
Modello Gemini usato per l'arricchimento |
PORT |
no | 8080 |
Porta su cui il server HTTP ascolta |
STATE_DIR |
no | ./state |
Cartella dove salvare i file di stato (catalog.json, enriched.json) |
CONNECTION_CHECK_INTERVAL_SECS |
no | 0 (manual) |
Intervallo in secondi tra un probe dei domini e il successivo. 0 = modalità manuale |
CATALOG_INTERVAL_SECS |
no | 0 (manual) |
Intervallo in secondi tra un auto-refresh del catalogo e il successivo. 0 = modalità manuale |
ENRICHMENT_INTERVAL_SECS |
no | 0 (manual) |
Intervallo in secondi tra un ciclo di auto-arricchimento e il successivo. 0 = modalità manuale |
ITA_TRACK_SCAN_INTERVAL_SECS |
no | 0 (manual) |
Intervallo in secondi tra uno scan ffprobe e il successivo per aggiornare ita_lang_audio_track_index da direct_url. 0 = modalità manuale |
INDEX_INTERVAL_SECS |
no | 0 (manual) |
Intervallo in secondi tra una ricostruzione dell'indice e la successiva. 0 = modalità manuale |
LOCAL_DOMAIN |
no | granarolo.local:3923 |
Dominio locale provato per primo durante la sync del catalogo |
INTERNET_DOMAIN |
no | dropbox.byschii.com |
Dominio fallback se il locale non risponde |
LUMIERE_URL |
no | — | URL base del server Lumiere da monitorare |
LUMIERE_CHECK_INTERVAL_SECS |
no | 0 (manual) |
Intervallo in secondi tra un check di raggiungibilità Lumiere e il successivo. 0 = modalità manuale |
ROOT_FOLDERS |
no | 4. cartoni, 6. film, 7. serie, 8. tvshow |
Cartelle root del catalogo separate da virgola; gli spazi sono accettati |
I tre check di stato
GET /api/status riporta tre campi che provengono da meccanismi distinti.
connection — probe della connessione di rete
Gestito da un goroutine in background (internal/domainmonitor/monitor.go). Ad ogni ciclo esegue probe HTTP nell'ordine:
GET http://<LOCAL_DOMAIN>/— se raggiungibile,connection = "local"GET https://<INTERNET_DOMAIN>/— se raggiungibile,connection = "internet"- Nessuno dei due risponde →
connection = "none"
Il primo probe parte subito all'avvio; i successivi seguono l'intervallo CONNECTION_CHECK_INTERVAL_SECS. Con 0 (modalità manuale) il probe non si ripete automaticamente ma può essere scatenato via POST /api/manual/monitor.
lumiere — raggiungibilità del server Lumiere
Gestito da un goroutine in background (internal/lumiere/lumiere.go). Ad ogni ciclo invia una richiesta HEAD a <LUMIERE_URL>/ e salva il risultato booleano in memoria (protetto da RWMutex). Il valore è esposto tramite IsReachable().
L'intervallo è controllato da LUMIERE_CHECK_INTERVAL_SECS. Con 0 (modalità manuale) il check non si ripete automaticamente ma può essere scatenato via POST /api/manual/lumiere.
clientOrigin — origine del client
Non è un check in background: viene calcolato in tempo reale ad ogni richiesta da isLocalRequest() in internal/server/server.go. Controlla semplicemente se l'header HTTP Host contiene .local o localhost:
"local"— richiesta proveniente dalla rete locale"remote"— richiesta proveniente da internet
Questo valore è usato per bloccare le azioni di scrittura (/api/play, /api/stop, cambio velocità) quando il client non è in rete locale.
Avvio
Modalità manuale (default)
Di default tutti i job sono in modalità manuale (*_INTERVAL_SECS=0). I job vanno attivati via API.
GEMINI_API_KEY=la-tua-chiave ./byschbuster
Modalità automatica
Imposta un intervallo positivo (in secondi) per ogni job che vuoi far girare in autonomia:
GEMINI_API_KEY=la-tua-chiave \
CONNECTION_CHECK_INTERVAL_SECS=60 \
LUMIERE_CHECK_INTERVAL_SECS=60 \
CATALOG_INTERVAL_SECS=900 \
ENRICHMENT_INTERVAL_SECS=1200 \
ITA_TRACK_SCAN_INTERVAL_SECS=60 \
INDEX_INTERVAL_SECS=1500 \
./byschbuster
API HTTP
GET /
Serve l'interfaccia web.
GET /api/status
Stato del server: dominio raggiunto e origine del client.
Risposta:
{
"connection": "local",
"clientOrigin": "local"
}
connection:"local"|"internet"|"none"— quale dominio ha risposto all'ultima syncclientOrigin:"local"|"remote"— determinato dall'headerHostdella richiesta (.local→ locale)
GET /api/search?q=<query>
Cerca nel catalogo per nome file, percorso logico o nome episodio (case-insensitive, substring). Restituisce max 50 risultati, arricchiti con i dati AI se disponibili.
Un file può contenere più episodi (es. S01E01-E03). In quel caso il campo episodi conterrà un elemento per ciascun episodio, multi_episodio sarà true e verrà aggiunto un warning.
curl "http://localhost:8080/api/search?q=breaking+bad"
Risposta (singolo episodio):
[
{
"name": "Breaking.Bad.S01E01.mkv",
"logical_path": "7. serie/Breaking Bad/Breaking.Bad.S01E01.mkv",
"direct_url": "https://dropbox.byschii.com/7. serie/...",
"root_folder": "7. serie",
"size": 1234567890,
"ts": 1715885468,
"enriched": true,
"serie": "Breaking Bad",
"anno": 2008,
"episodi": [
{
"stagione": 1,
"episodio": 1,
"nome": "Pilot",
"trama_breve": "...",
"trama_classica": "...",
"trama_estesa": "..."
}
]
}
]
Risposta (multi-episodio):
[
{
"name": "Breaking.Bad.S01E01-E03.mkv",
"logical_path": "7. serie/Breaking Bad/Breaking.Bad.S01E01-E03.mkv",
"direct_url": "https://dropbox.byschii.com/7. serie/...",
"root_folder": "7. serie",
"size": 3456789012,
"ts": 1715885468,
"enriched": true,
"serie": "Breaking Bad",
"anno": 2008,
"multi_episodio": true,
"warning": "Questo file contiene più episodi",
"episodi": [
{ "stagione": 1, "episodio": 1, "nome": "Pilot", "trama_breve": "...", "trama_classica": "...", "trama_estesa": "..." },
{ "stagione": 1, "episodio": 2, "nome": "Cat's in the Bag...", "trama_breve": "...", "trama_classica": "...", "trama_estesa": "..." },
{ "stagione": 1, "episodio": 3, "nome": "...And the Bag's in the River", "trama_breve": "...", "trama_classica": "...", "trama_estesa": "..." }
]
}
]
Se q è omesso, restituisce tutti i file (limitati a 50).
POST /api/play
Avvia la riproduzione in VLC. Solo da rete locale (header Host deve contenere .local o localhost).
curl -X POST http://byschbuster.local:8080/api/play \
-H 'Content-Type: application/json' \
-d '{"url": "https://dropbox.byschii.com/7. serie/.../file.mkv", "rate": false}'
Body:
{
"url": "https://dropbox.byschii.com/...",
"rate": true
}
url: deve avere hostgranarolo.local:3923odropbox.byschii.comrate:trueaggiunge--rate=1.1al comando VLC
Risposte:
200— riproduzione avviata400— URL non in whitelist o body malformato403— client non locale409— VLC è già in riproduzione
POST /api/stop
Termina VLC. Solo da rete locale.
curl -X POST http://byschbuster.local:8080/api/stop
Risposte:
200— VLC terminato403— client non locale500— VLC non in esecuzione o errore di segnale
API modalità manuale
Attivi sempre, ma utili principalmente con MANUAL_CATALOG=true e/o MANUAL_ENRICHMENT=true.
In modalità manuale, i rispettivi job non partono automaticamente — vanno scatenati a mano via API. Per sapere quali file mancano di arricchimento, usare /api/manual/unenriched.
POST /api/manual/monitor
Esegue un probe dei domini (locale e internet) e restituisce lo stato aggiornato.
curl -X POST http://localhost:8080/api/manual/monitor
Risposta:
{
"status": "probe completato",
"connection": "local",
"activeDomain": "granarolo.local:3923"
}
POST /api/manual/lumiere
Esegue un probe di raggiungibilità del server Lumiere e restituisce lo stato aggiornato.
curl -X POST http://localhost:8080/api/manual/lumiere
Risposta:
{
"status": "probe completato",
"lumiere": "reachable"
}
lumiere:"reachable"|"unreachable"— risultato del probe HEAD suLUMIERE_URL
POST /api/manual/catalog
Avvia una sync del catalogo in background.
curl -X POST http://localhost:8080/api/manual/catalog
Risposta:
{ "status": "catalog refresh avviato" }
POST /api/manual/unenriched
Restituisce le key dei file presenti nel catalogo ma non ancora arricchiti via Gemini.
curl -X POST http://localhost:8080/api/manual/unenriched
Risposta:
{
"status": "ok",
"unenriched": 3,
"total": 4820,
"keys": [
"a3f1c2d4...",
"b8e9f012...",
"cc34ab56..."
]
}
POST /api/manual/enrich
Arricchisce un singolo file via Gemini AI. Usare una key restituita da /api/manual/unenriched.
curl -X POST http://localhost:8080/api/manual/enrich \
-H 'Content-Type: application/json' \
-d '{"key": "a3f1c2d4..."}'
Body:
{ "key": "a3f1c2d4..." }
Risposta:
{
"status": "arricchimento completato",
"enriched": {
"key": "a3f1c2d4...",
"name": "Inception.2010.mkv",
"logical_path": "6. film/Inception.2010.mkv",
"direct_url": "https://dropbox.byschii.com/6. film/Inception.2010.mkv",
"root_folder": "6. film",
"size": 1234567890,
"ts": 1715885468,
"serie": "",
"anno": 2010,
"episodi": [
{
"nome": "Inception",
"trama_breve": "...",
"trama_classica": "...",
"trama_estesa": "..."
}
]
}
}
Errori:
400— key mancante nel body404— nessun file in catalogo con quella key500— errore Gemini o API key non impostata
Flusso tipico in modalità manuale
# 1. Sincronizza il catalogo (aspetta qualche secondo)
curl -X POST http://localhost:8080/api/manual/catalog
sleep 30
# 2. Vedi quali file non sono ancora arricchiti
curl -X POST http://localhost:8080/api/manual/unenriched
# 3. Arricchisci un file specifico per testare
curl -X POST http://localhost:8080/api/manual/enrich \
-H 'Content-Type: application/json' \
-d '{"key": "<key-dal-passo-2>"}'
# 4. Verifica il risultato
curl "http://localhost:8080/api/search?q=nome+del+file"
File di stato
Nella cartella STATE_DIR (default ./state):
| File | Contenuto |
|---|---|
catalog.json |
Lista piatta di tutti i file trovati sul dominio |
enriched.json |
Mappa key → EnrichedFile con i dati AI |