""" Enlaza streams de la cuenta secundaria (Javi) como respaldo en canales que solo tienen la principal (Boss). Uso (después de refrescar las listas M3U en Dispatcharr): cd /opt/dispatcharr && set -a && . ./.env && set +a && \\ ./.venv/bin/python manage.py shell < scripts/link_backup_streams_boss_javi.py Empareja por tvg-id; si falta, por nombre normalizado. Idempotente (no duplica enlaces). IDs de cuenta: revisa en UI M3U Accounts si cambian (por defecto Boss=2, Javi=4). """ import re from collections import defaultdict from apps.channels.models import Channel, ChannelStream, Stream BOSS_ACCOUNT_ID = 2 BACKUP_ACCOUNT_ID = 4 def norm_name(s): if not s: return "" return re.sub(r"\s+", " ", str(s).strip().lower()) def run(): by_tvg = {} by_name = defaultdict(list) javi_count = 0 for s in Stream.objects.filter( m3u_account_id=BACKUP_ACCOUNT_ID, is_stale=False ).iterator(chunk_size=500): javi_count += 1 tid = (s.tvg_id or "").strip() if tid and tid not in by_tvg: by_tvg[tid] = s by_name[norm_name(s.name)].append(s) linked = 0 skipped_has_backup = 0 skipped_primary_not_boss = 0 missing = 0 for ch in Channel.objects.prefetch_related("channelstream_set__stream").iterator( chunk_size=400 ): css = sorted(ch.channelstream_set.all(), key=lambda x: x.order) if not css: continue if any(cs.stream.m3u_account_id == BACKUP_ACCOUNT_ID for cs in css): skipped_has_backup += 1 continue boss = css[0].stream if boss.m3u_account_id != BOSS_ACCOUNT_ID: skipped_primary_not_boss += 1 continue match = None tid = (boss.tvg_id or "").strip() if tid and tid in by_tvg: match = by_tvg[tid] if match is None: cands = by_name.get(norm_name(boss.name)) if cands: match = cands[0] if match: max_order = max(cs.order for cs in css) _, created = ChannelStream.objects.get_or_create( channel=ch, stream=match, defaults={"order": max_order + 1}, ) if created: linked += 1 else: missing += 1 print( "[link_backup_streams] Javi streams indexados:", javi_count, "| nuevos enlaces:", linked, "| ya tenían backup:", skipped_has_backup, "| primer stream no es Boss:", skipped_primary_not_boss, "| sin pareja:", missing, ) run()