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

  1. natures/schemas.py — le natures album, audiolivro, podcast, episodio e canal ("opere di suono e immagine").
  2. services/obras.py — metadati dei file → sapere (e ritorno), gli episodi di una serie, il feed RSS.
  3. enriquecimento/metadados_rede.py — MusicBrainz e Cover Art Archive.
  4. services/canal.py — il palinsesto diventa una FUNZIONE dell'orologio (agora, programacao, diagnostico_fontes).
  5. services/transmissor.py — la programmazione diventa stream (HLS, Icecast).
  6. services/ao_vivo.py — RTMP, SRT e browser; la Sessao; la registrazione.
  7. api/v3/canais.py — le rotte; in bbx-make-deb, debian-tree/usr/share/baobaxia/nginx/baobaxia-rtmp.conf.
  8. Nel front: GradeEditor, SeletorFontes, BuscaSaberes, CanalAoVivo, CanaisNoAr, AoVivoConexao, AoVivoNavegador, e il Tocador.
  9. 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ì:

NaturePartiDove stanno
album, audiolivrotracce / capitoligli allegati, nell'ordine di data.ordem_conteudo; i dati di ciascuno in data.faixas[file]
podcastepisodiogni 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_fonte scende in una radice (anche dinamica e radice dentro radice, fino a PROFUNDIDADE), 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 per CACHE_SEG ed è single-flight — con tanta gente che apre la pagina, uno calcola e gli altri aspettano lo stesso risultato.
  • aleatorio mescola 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 (rotta GET /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. Il SeletorFontes lo 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 ha BBX_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_chave trova 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: AoVivoNavegador registra con MediaRecorder (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 alimentar mette solo in coda; un thread scrive; una folga (BBX_AO_VIVO_FOLGA_MB) limita la memoria; e bbx api parte con ws_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; durante encerrando, 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:

ValorePalinsestoDirettanginx :1935
on (predefinito)lo accende il gestoreaccettataaperto (con libnginx-mod-rtmp)
manualsolo bbx canal transmitiraccettataaperto
offnorifiutata (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

  • GradeEditor costruisce blocchi, rotazione, fuso orario, identità; SeletorFontes sceglie le fonti con il BuscaSaberes (il cercatore comune, con copertina, tipo, data e galassia) e mostra la diagnosi di ciascuna.
  • Nella pagina del canale, CanalAoVivo suona 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 Tocador pubblica 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 (get prima di metterlo nel palinsesto).
  • Hai toccato il palinsesto? Esegui test_canal_radio_tv.py e test_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) e test_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 nel README.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)?

ver este capítulo como saber