Parte III — Rami, linfa e semi
12 · I tamburi: opere sonore, canali e diretta
Il tamburo è la rete più antica: un colpo in un villaggio diventa risposta nell'altro. Qui Baobáxia prende voce — dischi e podcast con ordine e autorialità, radio e TV che suonano l'archivio secondo il palinsesto, e la diretta che entra nel mezzo e poi diventa sapere.
File da leggere
natures/schemas.py— le naturesalbum,audiolivro,podcast,episodioecanal("opere di suono e immagine").services/obras.py— metadati dei file → sapere (e ritorno), gli episodi di una serie, il feed RSS.enriquecimento/metadados_rede.py— MusicBrainz e Cover Art Archive.services/canal.py— il palinsesto diventa una FUNZIONE dell'orologio (agora,programacao,diagnostico_fontes).services/transmissor.py— la programmazione diventa stream (HLS, Icecast).services/ao_vivo.py— RTMP, SRT e browser; laSessao; la registrazione.api/v3/canais.py— le rotte; inbbx-make-deb,debian-tree/usr/share/baobaxia/nginx/baobaxia-rtmp.conf.- Nel front:
GradeEditor,SeletorFontes,BuscaSaberes,CanalAoVivo,CanaisNoAr,AoVivoConexao,AoVivoNavegador, e ilTocador. - Test:
test_obras_som.py,test_metadados_rede.py,test_canal_radio_tv.py,test_canal_identidade.py,test_ao_vivo.py,test_ao_vivo_navegador.py,test_ao_vivo_lento.py.
Opere: l'ordine e l'autorialità vivono nel sapere
Un midia con dieci mp3 non sa di essere un disco. Le natures di opera sì:
| Nature | Parti | Dove stanno |
|---|---|---|
album, audiolivro | tracce / capitoli | gli allegati, nell'ordine di data.ordem_conteudo; i dati di ciascuno in data.faixas[file] |
podcast | episodi | ogni episodio è un sapere con data.serie → il podcast, numero, temporada |
obras.do_arquivo mappa i metadati dentro il file (ID3, MP4, Vorbis): in un media, ciò che dice il file va al sapere intero; nell'album, ciò che è della TRACCIA (numero 1/12 → 1, titolo, artisti, compositori, testo) va alla riga di quel file, e sale solo ciò che è del DISCO (album, artista dell'album, anno, genere). Regola che i test custodiscono: ciò che la persona ha scritto non viene sovrascritto dalla lettura successiva.
Il ritorno è obras.para_arquivo: "scarica con dati" scrive titolo, N/totale, artisti e album in una COPIA (infra/metadados.py) — il file conservato nell'annex non cambia mai, altrimenti cambierebbe la sua chiave SHA256 e il sync vedrebbe un altro file.
feed_rss costruisce l'RSS del podcast solo con episodi pubblici, con link assoluti. I link assoluti hanno bisogno dell'indirizzo da cui la persona è arrivata: _origem_publica (api/v3/saberes.py) usa BBX_ORIGEM_PUBLICA, oppure l'X-Forwarded-Host che passa l'nginx del pacchetto.
MusicBrainz è una richiesta, non un automatismo: solo con internet, BBX_METADADOS_REDE=on, e completa senza cancellare ciò che c'è.
Il canale: la programmazione come funzione dell'orologio
Il cuore è nella docstring di services/canal.py: dato un canale e un istante, qualsiasi mucua e qualsiasi browser arrivano alla stessa risposta — quale file, di quale sapere, a quale secondo. Non c'è un server di stream per la "radio sincronizzata via web": il front chiede GET /canais/{uuid}/agora, suona il file dalla posizione indicata e chiede di nuovo quando finisce.
flowchart LR
G[data.grade<br/>blocchi: giorni, inizio, fine, fonti] --> B{bloco_em t?}
B -- sì --> S[_seq_do_bloco<br/>fonti → elementi, mescolare con seme fisso]
B -- no --> R[_seq_rotacao<br/>giro continuo dalla mezzanotte]
S --> V[_com_vinhetas<br/>un jingle ogni N elementi]
R --> V
V --> C[_no_ciclo: elemento + posizione]
C --> A[agora: elemento, posizione, a seguire]
Pezzi che vale la pena capire:
itens_da_fontescende in una radice (anche dinamica e radice dentro radice, fino aPROFUNDIDADE), un podcast (episodi in ordine), un album (tracce in ordine) o qualsiasi sapere (i suoi allegati). Entra solo ciò che è pubblico letto come anonimo e ha una durata misurata — e la durata si misura solo per i file presenti su questa mucua (repository.py). La radio suona audio e il suono dei video; la TV, solo video._sequenciaè costosa (legge saperi, percorre radici): resta in cache perCACHE_SEGed è single-flight — con tanta gente che apre la pagina, uno calcola e gli altri aspettano lo stesso risultato.aleatoriomescola con seme fisso (canale + blocco + giorno): tutti sentono lo stesso ordine "casuale".- La lista A seguire salta i buchi di silenzio: conta solo elementi veri (un vecchio bug lasciava la lista corta quando il palinsesto aveva pause).
diagnostico_fontes(rottaGET /canais/fontes) dice, per ogni fonte, quanti elementi e quanto tempo suonano — o il motivo per cui non suonano:sem_video,sem_duracao,nao_publico,nao_encontrado,vazio. IlSeletorFonteslo mostra sul chip; è la risposta a "perché la TV non suona il mio album?".
L'identità del canale sono gli allegati del canale stesso: data.logo (la copertina; con data.marca_dagua, anche la filigrana della TV, un overlay di ffmpeg) e data.vinhetas, inseriti ogni data.vinheta_a_cada elementi.
Il trasmettitore: dalla funzione allo stream
Solo la mucua proprietaria del canale (saber.mucua) trasmette, e solo con data.transmitir. Un gestore (iniciar_gerente, ogni INTERVALO) accende e spegne un Transmissor per canale; un lock su file ne garantisce uno solo, anche con l'API e bbx canal transmitir attivi insieme.
- Radio: un ffmpeg codificatore resta acceso tutto il tempo leggendo PCM; un ciclo lo alimenta con il file di turno (
-re, dalla posizione giusta) o con silenzio. Siccome non si ferma mai, chi ascolta non cade tra una traccia e l'altra. Uscita HLS sempre; Icecast se la mucua haBBX_ICECAST_URL(la password vive sulla mucua, mai nel sapere — il sapere viaggia). - TV: video grezzo nel tubo sarebbe troppo pesante; ogni file diventa segmenti HLS nella stessa lista, con discontinuità, a
BBX_CANAL_TV_ALTURA/KBPS.
Non intralciare la mucua. Suonare un canale non deve bloccare il sync né il database: il trasmettitore solo LEGGE (senza lock di scrittura), ffmpeg gira con nice (BBX_CANAL_NICE) e pochi core (BBX_CANAL_THREADS), e la conversione della registrazione gira a nice 19 con disco best-effort basso.
La diretta: tre porte, una Sessao
flowchart LR
OBS[OBS / app] -- RTMP :1935 --> N[nginx-rtmp] -- on_publish --> API[/ao-vivo/rtmp/publicar/]
N --> F
SRT[app SRT] -- UDP 9000-9019 --> F
NAV[browser<br/>MediaRecorder] -- WebSocket --> Q[coda d'ingresso<br/>+ thread scrittore] --> F
F[Sessao: un ffmpeg] --> H[HLS del canale]
F --> GR[registrazione] --> SB[sapere PRIVATO<br/>gravado_em → canale]
- RTMP: nginx riceve e chiede all'API (
on_publish, solo da 127.0.0.1) se la chiave è valida.canal_da_chavetrova il canale; la chiave vive solo in~/.baobaxia/canais/<uuid>/ao_vivo.chave— mai nel sapere. Chi modifica può sceglierne una propria (validar_chave: 10–64 di[A-Za-z0-9_-], unica sulla mucua → 409 se ripetuta). - SRT: un ffmpeg ascolta su una porta UDP per canale, con la chiave come passphrase.
- Browser:
AoVivoNavegadorregistra conMediaRecorder(WebM H.264 di preferenza; VP8 su Firefox; MP4 per ultimo), un fotogramma chiave ogni 2 s, e manda i pezzi dal WebSocket/ao-vivo/navegador/{uuid}. Cambiare fotocamera a metà usa un canvas "palco": il registratore non riparte.
Lezioni che sono costate cadute (leggi il git log):
- Mai bloccare l'handler del WebSocket. Aspettare ffmpeg al suo interno faceva perdere il ping a uvicorn e cadere la connessione (~35 s). Ora
alimentarmette solo in coda; un thread scrive; unafolga(BBX_AO_VIVO_FOLGA_MB) limita la memoria; ebbx apiparte conws_ping_timeout=60. - Niente ricodifica quando si può. L'H.264 che arriva (OBS, app, Chrome/Edge/Safari) è copiato nell'HLS e nella registrazione (
copiar_video_ao_vivo); solo il VP8 è ricodificato. La qualità scelta da chi trasmette è quella di chi guarda. - Il doppio clic creava due sessioni e la seconda uccideva la prima:
Transmissor._ocupado()nel back e il pulsante disattivato nel front. - Il silenzio è la fine: senza dati per
BBX_AO_VIVO_SILENCIO, la sessione finisce e tiene ciò che ha registrato; duranteencerrando,estado()dice già "non in diretta". - Il disco pieno abbatte la mucua: sotto
BBX_AO_VIVO_DISCO_MIN, la diretta non parte nemmeno.
La registrazione diventa sapere. guardar_gravacao: _converter copia soltanto quando l'H.264 è fino a 1,5× l'obiettivo (~2000+160 kbps, circa 1 GB/h); altrimenti lo rifà a quella misura. Nasce visibility: private (la vedono solo chi l'ha creata e i PAJÉ della mucua) con la relazione gravado_em → canale (e gravacao_de dall'altro lato), e, se il canale lo chiede, come episodio del podcast scelto.
Accendere e spegnere: BBX_CANAIS
Radio/TV è facoltativa. Il postinst chiede una volta ("Attivare radio/TV?") e scrive BBX_CANAIS:
| Valore | Palinsesto | Diretta | nginx :1935 |
|---|---|---|---|
on (predefinito) | lo accende il gestore | accettata | aperto (con libnginx-mod-rtmp) |
manual | solo bbx canal transmitir | accettata | aperto |
off | no | rifiutata (garantir → None, on_publish → 403, il WebSocket chiude con un motivo) | chiuso |
L'editor lo sa da ao-vivo-config (canais_na_mucua: false) e avvisa. I test girano in manual: il gestore non si accende da solo, ma la diretta è provata da capo a fondo.
Nel front
GradeEditorcostruisce blocchi, rotazione, fuso orario, identità;SeletorFontessceglie le fonti con ilBuscaSaberes(il cercatore comune, con copertina, tipo, data e galassia) e mostra la diagnosi di ciascuna.- Nella pagina del canale,
CanalAoVivosuona sincronizzato (o l'HLS, quando c'è una diretta), mostra i chip 📻/📺 e 🌐 UTC±x, e gli allegati restano raccolti. CanaisNoArè la fascia In onda ora della pagina iniziale (GET /canais/no-ar).- Il
Tocadorpubblica l'opera nella Media Session del sistema: copertina, traccia, artisti sullo schermo bloccato, pulsanti successivo/precedente.
Esercizi
- La durata si misura solo di ciò che è qui: per la TV di una mucua, l'archivio deve essere presente su di essa (
getprima di metterlo nel palinsesto). - Hai toccato il palinsesto? Esegui
test_canal_radio_tv.pyetest_canal_identidade.py— fissano l'orologio e controllano la programmazione elemento per elemento. - Hai toccato la diretta?
test_ao_vivo_navegador.py(WebSocket con un ffmpeg vero) etest_ao_vivo_lento.py(mucua lenta: la connessione non deve cadere). - Variabile nuova? Mettila in
docs/baobaxia-configuracao.md, nel tema giusto, indicando se è essenziale, comune o di regolazione fine — e nelREADME.Debian(sezione 4) solo se una mucua normale deve toccarla.
Da discutere
- La programmazione è deterministica perché tutte le mucua sentano la stessa cosa senza un server centrale. Quali altre parti di Baobáxia potrebbero essere "funzioni dell'orologio" invece di stato salvato?
- La registrazione nasce privata. Chi dovrebbe poterla pubblicare — chi ha trasmesso, chi cura il canale, il cerchio? Oggi il codice la lascia a chi l'ha creata e ai PAJÉ: basta?
- Una conferenza (più persone in video) può entrare in un canale come un'altra fonte RTMP. Cosa cambia — e cosa no — se un giorno Baobáxia integrasse una stanza di conversazione (LiveKit, OpenVidu)?
