Update station_parser.py

This commit is contained in:
2025-12-17 17:16:36 +00:00
parent fb273db3b1
commit 5812899b76

View File

@@ -1,10 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""
Radio Station Playlist Parser.
Supports parsing of M3U, M3U8, and PLS playlist formats to resolving
actual stream URLs. Handles recursive playlists and HLS stream detection.
"""
import re import re
import asyncio import asyncio
@@ -15,9 +9,7 @@ import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class StationParser: class StationParser:
"""Parses playlist files to extract the underlying media stream URL."""
def __init__(self, timeout: int = 10): def __init__(self, timeout: int = 10):
self.timeout = aiohttp.ClientTimeout(total=timeout) self.timeout = aiohttp.ClientTimeout(total=timeout)
@@ -38,13 +30,11 @@ class StationParser:
return None return None
def is_playlist_url(self, url: str) -> bool: def is_playlist_url(self, url: str) -> bool:
"""Checks if a URL points to a supported playlist format."""
parsed = urlparse(url.lower()) parsed = urlparse(url.lower())
path = parsed.path path = parsed.path
return any(path.endswith(ext) for ext in ['.m3u', '.m3u8', '.pls']) return any(path.endswith(ext) for ext in ['.m3u', '.m3u8', '.pls'])
def parse_m3u(self, content: str, base_url: str = "") -> List[Dict[str, str]]: def parse_m3u(self, content: str, base_url: str = "") -> List[Dict[str, str]]:
"""Parses M3U/M3U8 content."""
streams = [] streams = []
lines = content.strip().split('\n') lines = content.strip().split('\n')
@@ -69,7 +59,6 @@ class StationParser:
} }
continue continue
# Handle M3U8 stream attributes (bandwidth, resolution)
if line.startswith('#EXT-X-STREAM-INF:'): if line.startswith('#EXT-X-STREAM-INF:'):
attrs = self._parse_attributes(line[18:]) attrs = self._parse_attributes(line[18:])
current_info = { current_info = {
@@ -95,7 +84,6 @@ class StationParser:
return streams return streams
def parse_pls(self, content: str) -> List[Dict[str, str]]: def parse_pls(self, content: str) -> List[Dict[str, str]]:
"""Parses PLS INI-style content."""
streams = [] streams = []
entries = {} entries = {}
@@ -124,7 +112,6 @@ class StationParser:
return streams return streams
def _parse_attributes(self, attr_string: str) -> Dict[str, str]: def _parse_attributes(self, attr_string: str) -> Dict[str, str]:
"""Helper to parse key="value" attributes in M3U8 tags."""
attrs = {} attrs = {}
pattern = r'([A-Z-]+)=(?:"([^"]+)"|([^,]+))' pattern = r'([A-Z-]+)=(?:"([^"]+)"|([^,]+))'
for match in re.finditer(pattern, attr_string): for match in re.finditer(pattern, attr_string):
@@ -134,12 +121,6 @@ class StationParser:
return attrs return attrs
async def resolve_stream_url(self, url: str) -> Tuple[Optional[str], Optional[Dict]]: async def resolve_stream_url(self, url: str) -> Tuple[Optional[str], Optional[Dict]]:
"""
Recursively resolves a URL until a raw stream is found.
Returns:
Tuple containing the resolved URL and its metadata.
"""
if not self.is_playlist_url(url): if not self.is_playlist_url(url):
return url, {'original_url': url} return url, {'original_url': url}
@@ -147,7 +128,6 @@ class StationParser:
if not content: if not content:
return None, None return None, None
# Return the URL immediately if it's an HLS master playlist, as ffmpeg handles these.
if '#EXT-X-TARGETDURATION' in content: if '#EXT-X-TARGETDURATION' in content:
logger.info(f"Detected HLS Media Playlist: {url}") logger.info(f"Detected HLS Media Playlist: {url}")
return url, {'original_url': url, 'is_hls': True} return url, {'original_url': url, 'is_hls': True}
@@ -160,7 +140,6 @@ class StationParser:
if not streams: if not streams:
return None, None return None, None
# Default to the first stream, but prefer higher bandwidth for adaptive streams.
best_stream = streams[0] best_stream = streams[0]
if url.lower().endswith('.m3u8'): if url.lower().endswith('.m3u8'):
@@ -171,7 +150,6 @@ class StationParser:
stream_url = best_stream['url'] stream_url = best_stream['url']
# Recurse if the result is another playlist (nested playlists).
if self.is_playlist_url(stream_url): if self.is_playlist_url(stream_url):
return await self.resolve_stream_url(stream_url) return await self.resolve_stream_url(stream_url)