Procura‑se Estudante/Júnior — Legendas da TDT → IRC

Projeto técnico simples e contínuo. Remuneração por avença mensal com pagamento via PayPal. Local: Portugal (em zona coberta por TDT).

Teletexto 888 DVB‑T (TDT PT) Linux / Raspberry Pi FFmpeg / CCExtractor IRC PayPal

Resumo

Procuro estudante/jovem com facilidade em informática para montar e manter um pequeno sistema que capta as legendas do teletexto (pág. 888) dos 4 canais generalistas em Portugal (RTP1, RTP2, SIC, TVI) via TDT e as publica em tempo‑real em canais IRC do meu servidor irc.cubicpostcode.com:6667 (#rtp1, #rtp2, #sic, #tvi).

O que vais fazer

Requisitos mínimos

Valorizado (bónus)

Remuneração & disponibilidade

Local

Qualquer distrito em Portugal com receção TDT. Trabalho remoto no dia‑a‑dia, mas com acesso físico ao local onde fica a antena/recetor.

Hardware e Software (referência)

Hardware

  • Antena UHF para TDT (interior ou exterior consoante sinal).
  • Sintonizador DVB‑T USB (ex.: Hauppauge WinTV‑soloHD ou equivalente).
  • Raspberry Pi 4 (ou mini‑PC/PC com Linux).

Software

  • Linux (Debian/Ubuntu/Raspberry Pi OS).
  • dvbv5-utils, ffmpeg, ccextractor, python3.
  • (Opcional) Tvheadend para gestão via Web.

Notas técnicas (curto‑circuito)

Os 4 generalistas estão no mesmo MUX A. Um único tuner a debitar /dev/dvb/adapter0/dvr0 fornece o Transport Stream completo. O teletexto (pág. 888) vem como stream embebida em cada program; convertemos para texto temporizado.

# instalar
sudo apt update && sudo apt install -y dvbv5-utils ffmpeg ccextractor python3

# travar o MUX A (ex.: sintonizando RTP1) e expor TS em /dev/dvb/.../dvr0
sudo dvbv5-zap -a 0 -c /etc/dvb/channels.conf -r "RTP 1" &

# descobrir program numbers
ffprobe -hide_banner -loglevel error -show_programs /dev/dvb/adapter0/dvr0 | grep -E "program_id|service_name"

# pipeline por canal (ex.: RTP1) -> envia 888 para IRC
ccextractor /dev/dvb/adapter0/dvr0 \
  --program-number <PROG_RTP1> \
  --teletext-page 888 --out=srt --stdout | \
  python3 irc_subs_bot.py  # bot simples que publica no IRC

# servidor IRC de destino
irc.cubicpostcode.com:6667  (#rtp1, #rtp2, #sic, #tvi)

Legal & ética

O objetivo é apenas publicar legendas de acessibilidade (teletexto 888). Não há retransmissão de áudio/vídeo. Respeitamos direitos de transmissão e privacidade.

Como candidatar‑te

Envia mensagem pelo OLX/CustoJusto ou por e‑mail com o assunto “TDT → IRC (Avença)” com:

Contacto direto (substituir antes de publicar): [o_teu_email@dominio.pt] · [telemóvel] · Recebo via PayPal: [o.teu.paypal@exemplo.com]

Versão texto simples (para copiar/colar caso a plataforma remova HTML)

Dúvidas técnicas? Posso partilhar o bot IRC em Python e os unit files do systemd já prontos.

Pronto para publicar Ver bot & systemd

Pacote técnico: Bot IRC + serviços systemd

Clica em Copiar para levar o ficheiro. Podes colar diretamente no servidor. Ajusta <PROG_...> e canais/credenciais antes de ativar.

/opt/irc_subs_bot.py
#!/usr/bin/env python3
# Bot IRC para publicar legendas (SRT pela stdin) em canal IRC
# Env vars: IRC_HOST, IRC_PORT, IRC_NICK, IRC_USER, IRC_PASS, IRC_CHAN,
#           RATE_DELAY (s), PREFIX (ex: [RTP1]), TIMESTAMP (1/0), IRC_TLS (1/0)
import os, sys, socket, time, datetime

IRC_HOST=os.getenv("IRC_HOST","irc.cubicpostcode.com")
IRC_PORT=int(os.getenv("IRC_PORT","6667"))
IRC_TLS=os.getenv("IRC_TLS","0")=="1"
IRC_NICK=os.getenv("IRC_NICK","subsbot")
IRC_USER=os.getenv("IRC_USER","subsbot")
IRC_PASS=os.getenv("IRC_PASS","")
IRC_CHAN=os.getenv("IRC_CHAN","#subs")
RATE_DELAY=float(os.getenv("RATE_DELAY","0.8"))
ADD_PREFIX=os.getenv("PREFIX","")
ADD_TS=os.getenv("TIMESTAMP","1")=="1"

def irc_send(sock,msg):
    sock.send((msg+"
").encode("utf-8",errors="ignore"))

def connect():
    s=socket.socket()
    if IRC_TLS:
        import ssl
        ctx=ssl.create_default_context()
        s=ctx.wrap_socket(s,server_hostname=IRC_HOST)
    s.connect((IRC_HOST,IRC_PORT))
    if IRC_PASS:
        irc_send(s,f"PASS {IRC_PASS}")
    irc_send(s,f"NICK {IRC_NICK}")
    irc_send(s,f"USER {IRC_USER} 0 * :{IRC_USER}")
    # Handshake + JOIN
    buf=b""; joined=False
    s.settimeout(300)
    while not joined:
        chunk=s.recv(2048)
        if not chunk: raise ConnectionError("disconnected during handshake")
        buf+=chunk
        for line in buf.split(b"
"):
            if not line: continue
            if line.startswith(b"PING"):
                irc_send(s,"PONG "+line.split()[1].decode())
            if b" 001 " in line: # welcome
                irc_send(s,f"JOIN {IRC_CHAN}")
                joined=True
        buf=b""
    return s

def srt_text_lines(stream):
    last=""
    for raw in stream:
        t=raw.strip()
        if not t or t.isdigit() or "-->" in t: # ignora índices e timestamps
            continue
        if t==last: # evita duplicados consecutivos
            continue
        last=t
        yield t

def main():
    while True:
        try:
            s=connect()
            for line in srt_text_lines(sys.stdin):
                msg=line[:400]+("…" if len(line)>400 else "")
                if ADD_TS:
                    ts=datetime.datetime.now().strftime("[%H:%M:%S]")
                    msg=(f"{ADD_PREFIX} " if ADD_PREFIX else "")+ts+" "+msg
                elif ADD_PREFIX:
                    msg=f"{ADD_PREFIX} "+msg
                irc_send(s,f"PRIVMSG {IRC_CHAN} :{msg}")
                time.sleep(RATE_DELAY)
        except Exception:
            time.sleep(3)
            continue

if __name__=="__main__":
    main()
/etc/systemd/system/tdt-mux.service
[Unit]
Description=TDT MUX lock (dvbv5-zap -r)
After=network-online.target

[Service]
ExecStart=/usr/bin/dvbv5-zap -a 0 -c /etc/dvb/channels.conf -r "RTP 1"
Restart=always
RestartSec=2

[Install]
WantedBy=multi-user.target
/etc/systemd/system/rtp1-subs.service
[Unit]
Description=Legendas RTP1 -> IRC #rtp1
After=tdt-mux.service

[Service]
Environment=IRC_HOST=irc.cubicpostcode.com
Environment=IRC_PORT=6667
Environment=IRC_NICK=rtp1_subs
Environment=IRC_USER=rtp1_subs
Environment=IRC_CHAN=#rtp1
Environment=PREFIX=[RTP1]
Environment=TIMESTAMP=1
ExecStart=/bin/sh -c 'ccextractor /dev/dvb/adapter0/dvr0 --program-number <PROG_RTP1> --teletext-page 888 --out=srt --stdout | /opt/irc_subs_bot.py'
Restart=always
RestartSec=2

[Install]
WantedBy=multi-user.target
/etc/systemd/system/rtp2-subs.service
[Unit]
Description=Legendas RTP2 -> IRC #rtp2
After=tdt-mux.service

[Service]
Environment=IRC_HOST=irc.cubicpostcode.com
Environment=IRC_PORT=6667
Environment=IRC_NICK=rtp2_subs
Environment=IRC_USER=rtp2_subs
Environment=IRC_CHAN=#rtp2
Environment=PREFIX=[RTP2]
Environment=TIMESTAMP=1
ExecStart=/bin/sh -c 'ccextractor /dev/dvb/adapter0/dvr0 --program-number <PROG_RTP2> --teletext-page 888 --out=srt --stdout | /opt/irc_subs_bot.py'
Restart=always
RestartSec=2

[Install]
WantedBy=multi-user.target
/etc/systemd/system/sic-subs.service
[Unit]
Description=Legendas SIC -> IRC #sic
After=tdt-mux.service

[Service]
Environment=IRC_HOST=irc.cubicpostcode.com
Environment=IRC_PORT=6667
Environment=IRC_NICK=sic_subs
Environment=IRC_USER=sic_subs
Environment=IRC_CHAN=#sic
Environment=PREFIX=[SIC]
Environment=TIMESTAMP=1
ExecStart=/bin/sh -c 'ccextractor /dev/dvb/adapter0/dvr0 --program-number <PROG_SIC> --teletext-page 888 --out=srt --stdout | /opt/irc_subs_bot.py'
Restart=always
RestartSec=2

[Install]
WantedBy=multi-user.target
/etc/systemd/system/tvi-subs.service
[Unit]
Description=Legendas TVI -> IRC #tvi
After=tdt-mux.service

[Service]
Environment=IRC_HOST=irc.cubicpostcode.com
Environment=IRC_PORT=6667
Environment=IRC_NICK=tvi_subs
Environment=IRC_USER=tvi_subs
Environment=IRC_CHAN=#tvi
Environment=PREFIX=[TVI]
Environment=TIMESTAMP=1
ExecStart=/bin/sh -c 'ccextractor /dev/dvb/adapter0/dvr0 --program-number <PROG_TVI> --teletext-page 888 --out=srt --stdout | /opt/irc_subs_bot.py'
Restart=always
RestartSec=2

[Install]
WantedBy=multi-user.target
Setup rápido (comandos)
# pacotes
sudo apt update && sudo apt install -y dvbv5-utils ffmpeg ccextractor python3

# bot
sudo install -m 0755 -D /tmp/irc_subs_bot.py /opt/irc_subs_bot.py  # cola o ficheiro em /tmp antes

# sintonizar MUX A
sudo tee /etc/systemd/system/tdt-mux.service >/dev/null <<'EOF'
[Unit]
Description=TDT MUX lock (dvbv5-zap -r)
After=network-online.target
[Service]
ExecStart=/usr/bin/dvbv5-zap -a 0 -c /etc/dvb/channels.conf -r "RTP 1"
Restart=always
RestartSec=2
[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now tdt-mux.service

# descobrir PROGRAM_NUMBER por canal (sem sed/backrefs)
ffprobe -hide_banner -loglevel error -show_programs /dev/dvb/adapter0/dvr0 \
  | awk -F= '/^program_id=/{print "PROGRAM "$2} /^tags=service_name=/{sub(/^tags=service_name=/,""); print "NAME "$0}'

# criar os serviços de cada canal (usa os ficheiros acima)
sudo systemctl enable --now rtp1-subs.service rtp2-subs.service sic-subs.service tvi-subs.service

TLS: define IRC_TLS=1 e usa a porta TLS do teu servidor (ex.: 6697).