Projeto técnico simples e contínuo. Remuneração por avença mensal com pagamento via PayPal. Local: Portugal (em zona coberta por TDT).
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
).
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
Software
dvbv5-utils
, ffmpeg
, ccextractor
, python3
.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)
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.
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]
Dúvidas técnicas? Posso partilhar o bot IRC em Python e os unit files do systemd já prontos.
Clica em Copiar para levar o ficheiro. Podes colar diretamente no servidor. Ajusta <PROG_...> e canais/credenciais antes de ativar.
#!/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()
[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
[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
[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
[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
[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
# 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).