Recognize raw audio/video streams, not just playlists, as alive

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).
pull/1169/head
Kálmán „KAMI” Szalai 2026-09-05 20:55:54 +02:00
parent f2a1e1f9d4
commit b05f8ef87f
1 changed files with 14 additions and 0 deletions

View File

@ -124,6 +124,17 @@ def looks_like_a_playlist(head):
return "<MPD" in text[:2000]
# some radio stations serve the live audio itself at the listed URL rather than
# a playlist that points to one - that is a legitimate live source too, and a
# station is not "gone" just because it skipped the manifest step
STREAM_CONTENT_TYPES = ("audio/", "video/mp2t", "video/mpeg")
def looks_like_a_raw_stream(content_type):
"""Return True if `content_type` is a direct audio/video stream, not a manifest."""
return content_type.lower().startswith(STREAM_CONTENT_TYPES)
def probe(url, timeout):
"""Open `url` once and report what the server did."""
request = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
@ -133,6 +144,7 @@ def probe(url, timeout):
context.verify_mode = ssl.CERT_NONE
try:
with urllib.request.urlopen(request, timeout=timeout, context=context) as response:
content_type = response.headers.get("Content-Type", "")
head = response.read(3000).decode("utf-8", "ignore")
except urllib.error.HTTPError as error:
if error.code in GONE_CODES:
@ -145,6 +157,8 @@ def probe(url, timeout):
# response and then hung up mid-chunk (IncompleteRead) and similar
# low-level protocol violations - a broken connection, not a bad URL
return UNREACHABLE
if looks_like_a_raw_stream(content_type):
return OK
return OK if looks_like_a_playlist(head) else GONE