check_channels.py's exit code reflects every dead channel across the
whole list file it was pointed at, not just the ones a PR added or
changed. Since pr_check.yml ran the checker directly and let its exit
code decide the job's pass/fail, any PR touching a list that already
had pre-existing dead entries (which is most of them - dozens of
already-known-broken channels exist across these lists) got a red X
for something it never caused, even though the workflow's own summary
step already computed the right "touched vs already broken" split.
Fixes this by:
- letting the check step run to completion regardless of its exit
code (the checker's own verdict on the whole file isn't this job's
call to make)
- moving pass/fail into the summarize step, which already has the
touched/others split: the job now fails only when a channel this PR
itself added or changed comes back `dead`
- deliberately not failing on `blocked`/`unreachable`/`flaky` for
touched channels either - these have real false-positive rates from
the runner's own network (geo-blocks, a datacenter IP some providers
reject, one slow probe), which is exactly why the *Channel check
(deep)* workflow exists to give `dead`-looking channels a second
opinion before anyone acts on them
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Enus8N247jHM5r5Som5JuU
Kenya:
- NTV Kenya: nation.africa/kenya/tv/live 404s. Their live simulcast is on
the station's own official YouTube channel (linked from ntvkenya.co.ke
itself); confirmed actually live at verification time (canonical URL
resolved to a /watch?v= page, not the channel page).
Cyprus:
- Kibris TV: the shared sc-kuzeykibrissmarttv.ercdn.net CDN is gone
(DNS NXDOMAIN) for all 6 TRNC channels in this section. Found Kibris
TV's current feed via a Northern Cyprus web-TV aggregator
(old.kuzeykibris.tv), a real rolling HLS media playlist. Verified
alive via check_channels.py (3/3 ok).
- TV 2020: same dead CDN. Its official YouTube channel is confirmed
live now. BRT 1, BRT 2, and Kibris Genc TV/Kanal T were also
researched, but their YouTube channels were NOT live at verification
time (canonical stayed on the channel page) - left unchanged rather
than adding an unreliable "channel not live" link.
Argentina:
- Telefe: the old telefe.com API endpoint is deprecated (redirects to
mitelefe.com, whose live-stream URL is now fetched client-side by JS
and not extractable via curl). Replaced with a verified-alive
third-party restream, the same pattern already used for other
channels in this file (e.g. El Nueve's raw-IP entry). Flagged here
as a fallback, not an official source.
- Aunar, Cine.AR, Tec TV, DeporTV: researched, all confirmed genuinely
dead at their own official infrastructure (squatted/parked domains,
VOD-only platforms, abandoned servers) with no live YouTube
simulcast either - left unchanged, no fix available.
Regenerated playlist.m3u8 and the per-country playlists via
make_playlist.py.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Enus8N247jHM5r5Som5JuU
- TV 7 Albania: vs.sednastream.com:1936 returns HTTP 403. Found the
station's current feed via its official site's live embed (tv7.al ->
swf.tulix.tv iframe), backed by bozztv.com. Verified alive via
check_channels.py (3/3 ok), real HLS media playlist with rolling segments.
- TV Apollon: the stored token in the old URL is stale (404, wrong path
casing). Found the current token+path hardcoded in the station's own
live player JS on apollon.tv. Verified alive via check_channels.py
(3/3 ok), real live playlist with current-timestamped segments.
Researched but left unchanged (genuinely unfixable within reasonable
effort - documented for future reference):
- Albania: Kanali 7, Alpo TV, Panorama TV, Report TV - official sites
either have no public video stream, are behind a Cloudflare JS
challenge, or the stream host itself is unreachable.
- Bulgaria (bTV, Nova TV, Bulgaria On Air, Nova News, BNT1-4): official
infrastructure appears to be Bulgaria-ISP-local or geo-blocked; Nova's
own embed uses a per-request, IP-bound, time-limited signed URL, not
usable as a static playlist entry.
- Georgia (Adjara TV, Maestro TV, Mtavari Arkhi, GDS, Pos TV, Obieqtivi):
official domains are down, parked/expired, or return gateway errors
even on the broadcaster's own site.
Regenerated playlist.m3u8 and playlists/playlist_albania.m3u8 via
make_playlist.py.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Enus8N247jHM5r5Som5JuU
Same nexttv.ht.hr CDN as the already-marked HRT 4: reachable and serving
real DASH content from an EU/Croatian vantage point, but geo-restricted
for other regions (confirmed by the CI runner's own check run on this PR
reporting all four HRT channels, including the unchanged HRT 4, as dead).
Adds the existing Ⓖ convention to the three new URLs for consistency.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Enus8N247jHM5r5Som5JuU
Croatia:
- HRT 1, HRT 2, HRT 3: the bhtelecom.ba mirror (webtvstream.bhtelecom.ba)
no longer serves these streams. Switched to HRT's own official CDN
(bpcdnmanprod.nexttv.ht.hr/bpk-tv/<channel>/default/index.mpd), the same
origin already used successfully for the existing HRT 4 entry. Verified
alive via check_channels.py (3/3 ok) before committing.
- Diadora: researched, confirmed genuinely dead at its own stream origin;
no working alternative found, left unchanged.
Czech Republic:
- Praha TV: stream.polar.cz/prahatv no longer resolves/serves; found the
station's current stream on Praha TV's own site, backed by CDN77
(1977134029.rsc.cdn77.org). Verified alive via check_channels.py.
- CNN Prima News, Prima MAX, CS Film, Šlágr Muzika: researched, confirmed
DRM-gated or dead at source with no viable public alternative; left
unchanged.
Regenerated playlist.m3u8 and per-country playlists via make_playlist.py.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Enus8N247jHM5r5Som5JuU
SVT 1/2/24/Kunskapskanalen: the old ed2.cdn.svt.se host is fully retired
(plain 404, not a geo-block response). SVT's own site now serves live
channels via api.svt.se/videoplayer-api/video/ch-<id>, which points at
svt-live-channel.akamaized.net - confirmed current by fetching that API
live. The new URLs are still geo-blocked to Sweden (Akamai 403, not a
404), so the Ⓖ marker stays; this is a URL update, not a fix for the
geo-restriction itself. No unrestricted third-party mirror exists for
SVT (checked; other IPTV list maintainers hit the same block).
Öppna Kanalen Stockholm and Malmö: marked [x]. Solidtango (their
hosting platform) now requires per-session signed URLs platform-wide -
Stockholm's old unsigned URL returns 402 Payment Required. Malmö's
entire subdomain 404s on every path; their site now points at a
dedicated app plus cable-only distribution, no public web stream at
all.
Öppna Kanalen Skövde and Västmanlands TV were also researched: both
are alive, but only through freshly-signed URLs that expire in ~24h -
not left unchanged deliberately, since committing today's signed link
would just go dead again tomorrow. A durable fix would need scraping
their live pages (okskovde.solidtango.com/live/livetv and
vastmanland.solidtango.com/live/vastmanlands-television--2) for a
fresh link periodically, not a static list entry.
The old player.smotrim.ru/iframe/stream/live_id/N embeds are all dead -
Smotrim's site redesign replaced them with a client-side API
(player-api.smotrim.ru/api/v1/channel/{id}) that returns the current
stream URL. Traced the old numeric id -> new channel id -> stream
mapping from the site's own JS bundle:
- Россия 1 (id 2961 -> channel 1): live.smotrim.ru, no signature, should
be durable
- Россия-Культура (id 19201 -> channel 4): same, unsigned
- Москва 24 (id 1661 -> channel 76): stream.smotrim.ru, carries a `sign`
query param
- Вести ФМ (id 52035 -> channel 199): same, signed
- Соловьёв Live (id 63338 -> channel 292): same, signed
The three signed URLs were stable across repeated fetches in testing
(not IP-bound), but could still rotate server-side - if the checker
flags one of these dead again, re-derive it from
player-api.smotrim.ru/api/v1/channel/{id} rather than assuming the
channel itself is gone.
Not fixed (researched, no working replacement found):
- РТР-Планета: removed from Smotrim entirely - VGTRK now describes it
as satellite-only for the diaspora, no web stream exists
- Катунь 24 / Кубань 24: both stations' own sites just embed the same
dead OK.ru video; OK.ru's underlying stream URL is IP-locked and
expires in ~1 hour, unusable as a static list entry
- Саратов 24: dropped its web player entirely, now Telegram-only
Root cause: Bauer Media Audio Ireland (Today FM, 98FM, Newstalk, Spin
1038, Classic Hits) migrated their stream infrastructure from
stream.audioxi.com to live-bauerie.sharp-stream.com (same mount names,
new host), found via their goloudplayer.com player pages. Sunshine
106.8 moved off sharp-stream onto streamtheworld.com (shared player
config with Nova and Classic Hits' own sites).
FM104 and Dublin's Q102 URLs are unchanged - they were re-verified
returning real audio and appear to have been a transient outage, not
a dead link (worth noting in case #1169's raw-audio-stream detection
fix was also masking these two as false negatives previously).
Note: these URL changes were verified for real audio content (bytes +
correct Content-Type) directly with curl, since check_channels.py
in this repo's history does not yet recognize raw audio/video streams
as alive (see #1169) - verify against that fix once merged.
Some radio stations serve the live audio directly at the listed URL
(Content-Type: audio/mpeg, audio/aacp, etc.) rather than an HLS/DASH
manifest that points to one. probe() only checked for #EXTM3U/<MPD in
the response body, so every one of these was misclassified as dead
regardless of whether the station actually worked.
Found while verifying replacement URLs for Irish radio stations: curl
confirmed real audio bytes with correct audio/* Content-Type on all of
them, but check_channels.py reported every single one as dead. Now
checks the response's Content-Type first; a direct audio/video stream
counts as alive without needing playlist syntax. Verified this doesn't
regress playlist detection (existing HLS/DASH channels still classify
correctly).
Found via each station's own official "diretta/streaming" page, following
embedded iframes/JS one level deeper to the real .m3u8. No hosting
infrastructure was brute-forced. Verified 3x each with check_channels.py,
all consistently alive:
- Travel TV, Donna TV, Alma TV, Italia 7, Lazio Tv, Gold Tv: new
5f11919dca3bd.streamlock.net host (old streaming.softwarecreation.it
provider dead)
- Calabria Uno Tv: same host, working path (ngrp: prefix dropped)
- Canale 21 / Canale 21 Extra: new encoders.immergo.tv provider (old
msvdn.net dead)
- Fascino Tv, Motori Tv: new 64b16f23efbee.streamlock.net host
- Ran Friul: azotosolutions.com load-balancer (old streamlock.net host dead)
- Love in Venice: same host, working path (.stream suffix added)
- Mediterranea Tv: new xdevel.com stream (old aswifi.it dead)
Castrovillari Tv and Cittaceleste Tv are still on their original URLs
unchanged - the outage looks to have been transient, both returned
live content again on retest.
Sportitalia SOLOCALCIO: found a candidate at
distribution.sportitalialive.it/sportitalia/sisolocalcio_abr/playlist.m3u8
(hardcoded in the official player page), but it returns 403 from here -
looks like a datacenter/ASN block rather than a bad URL, so left
unchanged rather than swap in something equally unverifiable.
19 other stations were also researched but had no findable working
replacement (parked/dead domain, JS-rendered player with no static URL,
or the official embed itself already dead) - left unchanged.
Found via each station's own official "diretta/streaming" page, following
embedded iframes (several use a shared white-label player at
player.streamshow.it/hosted/*.php) one level deeper to the real .m3u8.
No hosting infrastructure was brute-forced. Verified 3x each with
check_channels.py, all consistently alive:
- TVL (TV Libera Pistoia): new mariatvcdn.it host (old one dead)
- Teatro Tv: teatrotv.iostream.it (old m.iostream.it path dead)
- Tele Belluno: new mariatvcdn.it host + path
- Tele Cupole: azotosolutions.com load-balancer (old newradio.it dead)
- Tele Ischia: new streamlock.net host
- Tele Radio Ercolano: same saiuzwebnetwork.it provider, working path
- Teleiblea: same streamlock.net provider, working host
- Vera Tv: streamshow.it CDN (old shared streamshow.it host dead)
TG Norba 24 was listed twice under two different EPG rows with the same
tgv-id (TGNorba24.it) - one already pointed at a working
live-telenorba.cdn.netrw.it URL, the other (router.xdevel.com) was dead.
Removed the dead duplicate rather than adding a third entry.
29 other Italian local stations were also researched but had no
findable working replacement (site down, JS-injected player with no
static URL, or official embed itself already dead) - left unchanged.
mediapolis.rai.it's relinker service is now Italy-IP-gated: with
forceUserAgent it 302s to a "video not available" placeholder MP4
(the "raw binary" symptom this repo's checker was seeing), without it
Akamai returns a plain 403. RAI's own official pages (raiplay.it) still
embed the same dead relinker pattern, and no working parameter/domain
variant was found - this isn't a broken link, it's a geo-restriction
that happens to also break for the CI runner's non-Italian IP.
Found a third-party CDN mirror (d3k8wzt41aflvx.cloudfront.net) already
re-serving RAI's linear feeds unrestricted, in the same vein as the
existing Rai4K/UniNettuno akamaized.net mirrors already in this list.
Verified each with curl (multi-quality master playlist, real segment
references) and with check_channels.py (3x each): Rai 2/3/Premium/
Storia/Scuola came back consistently alive, Rai 1 flaky (2/3 ok, real
content each time) - consistent with general CDN flakiness elsewhere
in this list, not a sign the URL is wrong.
Dropped the Ⓖ marker on these 6 since the mirror isn't geo-blocked.
Rai 4, Rai 5, Rai Movie, Rai Gulp, Rai YoYo, Rai Sport, and Rai Radio 2
Visual Radio are not on this mirror and were left unchanged (still
pointing at the geo-gated relinker, since that's genuinely the current
official source and may still work for viewers with an Italian IP).
Both use the same lexanetwork.com infrastructure as FehérvárTV (#1163)
and Kapos TV (#1164). Found via each station's own official live page
(estv.hu homepage, komaromtv.hu/online-adas/), which embeds a player
resolving to a working relay URL. Verified 4x each, consistently alive.
Also searched ~45 other Hungarian local TV stations for lexanetwork.com
usage - none of the others use it (mostly WordPress/YouTube embeds, or
the station's own site is dead), so no further additions from this pass.
Was marked [x] (dead relay01/livestream004.sdp on cloudfront63). Found
the current URL the same way as FehérvárTV (#1163): the official site's
"Élő adás" page embeds an iframe at cloudfront63.lexanetwork.com/itplayer/
LIVE016_inc.php, which resolves to a real master playlist (bitrate/
resolution/codec info + segment reference) on a different host/path -
relay01/broadcast004.sdp on cloudfront44. Verified 4x, consistently alive.
Also checked the site's other dead ([x]) lexanetwork.com entries this
same way: AlföldTV's domain now redirects to a news portal with no live
player, Zugló TV's and Bajai TV's sites don't resolve/load at all, and
Lóverseny közvetítés is an event-only feed (found a newer loverseny3.sdp
path via the same method, but it's only live during actual races, so
nothing to verify against right now). None of those looked fixable.
The old URL (freerelay/fehervartv.sdp) answered HTTP 200 with a
technically-valid HLS header but nothing else - no #EXT-X-STREAM-INF,
no segment reference, just two lines. That passes a header-only check
(and did - the current checker reports it "alive") while being
unplayable in any real player, since there's nothing to play.
Found the working URL by fetching the official player embed at
https://www.fehervartv.hu/elo_adas (an iframe pointing at the same
lexanetwork.com host, but a different relay path - relay44_1/HDE046.sdp
- that does return a real master playlist with bitrate/resolution/codec
info and a segment reference). Verified 4x, consistently alive.
Follow-up to #1156. That PR found working cids (01, 08, 12 - the range
was 02-07,10,11 before) for channels previously marked [NO PUBLIC
STREAM], which is valuable, but it also reassigned the cid of every
already-known-working channel (NHK BS, BS日テレ, BS朝日, BS-TBS,
BSテレ東, BSフジ) to its neighbor's old value - a clean cyclic shift
across exactly six channels, which is what a list/list zip misaligned
by one produces, not what channel-by-channel verification produces.
Neither the old nor the new mapping carries any content-level proof
(the streams have no identifying metadata), so there's no way to tell
which is right - only that the shift itself isn't evidence of anything.
This takes only the part that's unambiguous: three cids nobody had
before, given to the three channels that had nothing. bs07 was one of
the "new" ones in #1156, but it's already BS10スターチャンネル's cid,
so WOWOWシネマ gets bs08 instead - the only other free value.
Verified each new cid resists a single flaky read (checked 4x): bs01
and bs12 came back flaky (3/4 ok) but real, bs08 came back alive (4/4).
That's consistent with how naori-test.netgenx.site behaves generally
(see check_channels.py's docstring), not a sign these three are wrong.
Replaces iptv-checker (npm + ffmpeg) with check_channels.py:
- Checks every channel in a list a PR touches, existing and new, not just
the added/changed rows - a PR editing italy.md now surfaces already-dead
channels in italy.md too, not only the one line it changed.
- Uses check_channels.py's dead/blocked/unreachable/flaky states instead
of a binary online/failed, so the summary no longer needs the "some of
these failures are geo-blocks, use your judgment" disclaimer without
telling reviewers which failures those are - blocked channels are now
named as such.
- Summary separates what the PR actually added/changed from what was
already broken in the same file, so a reviewer isn't left guessing
whether a failure is theirs to fix.
- No ffprobe pass here (kept fast for a PR gate) - anything this flags as
dead gets a second opinion from the scheduled Channel check (deep)
workflow before anyone acts on it.
Verified end to end against a simulated PR diff.
The first scheduled run of #1157's workflows failed outright: a server
started a chunked HTTP response and then hung up mid-chunk, which
urllib surfaces as http.client.IncompleteRead - a subclass of
http.client.HTTPException, not of OSError/URLError/ValueError, so
probe()'s except clause let it propagate and took the whole run down
before it wrote anything to --json, including check_channels_fast.yml's
run.jsonl.
Catch http.client.HTTPException alongside the existing exceptions and
treat it as unreachable, same as any other broken connection. Verified
against the exact exception (mocked) and against the live lists.