mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-23 02:35:14 +00:00
M3U Parse Fix
Updated how M3U files are parsed. Added preferred-region to core settings.
This commit is contained in:
parent
ff81f3614d
commit
3caeae5177
2 changed files with 57 additions and 31 deletions
|
|
@ -16,6 +16,34 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
LOCK_EXPIRE = 120 # Lock expires after 120 seconds
|
||||
|
||||
def parse_extinf_line(line: str) -> dict:
|
||||
"""
|
||||
Parse an EXTINF line from an M3U file.
|
||||
This function removes the "#EXTINF:" prefix, then splits the remaining
|
||||
string on the first comma that is not enclosed in quotes.
|
||||
|
||||
Returns a dictionary with:
|
||||
- 'attributes': a dict of attribute key/value pairs (e.g. tvg-id, tvg-logo, group-title)
|
||||
- 'display_name': the text after the comma (the fallback display name)
|
||||
- 'name': the value from tvg-name (if present) or the display name otherwise.
|
||||
"""
|
||||
if not line.startswith("#EXTINF:"):
|
||||
return None
|
||||
content = line[len("#EXTINF:"):].strip()
|
||||
# Split on the first comma that is not inside quotes.
|
||||
parts = re.split(r',(?=(?:[^"]*"[^"]*")*[^"]*$)', content, maxsplit=1)
|
||||
if len(parts) != 2:
|
||||
return None
|
||||
attributes_part, display_name = parts[0], parts[1].strip()
|
||||
attrs = dict(re.findall(r'(\w+)=["\']([^"\']+)["\']', attributes_part))
|
||||
# Use tvg-name attribute if available; otherwise, use the display name.
|
||||
name = attrs.get('tvg-name', display_name)
|
||||
return {
|
||||
'attributes': attrs,
|
||||
'display_name': display_name,
|
||||
'name': name
|
||||
}
|
||||
|
||||
def _get_group_title(extinf_line: str) -> str:
|
||||
"""Extract group title from EXTINF line."""
|
||||
match = re.search(r'group-title="([^"]*)"', extinf_line)
|
||||
|
|
@ -117,26 +145,21 @@ def refresh_single_m3u_account(account_id):
|
|||
for line in lines:
|
||||
line = line.strip()
|
||||
if line.startswith('#EXTINF'):
|
||||
tvg_name_match = re.search(r'tvg-name="([^"]*)"', line)
|
||||
tvg_logo_match = re.search(r'tvg-logo="([^"]*)"', line)
|
||||
# Extract tvg-id
|
||||
tvg_id_match = re.search(r'tvg-id="([^"]*)"', line)
|
||||
tvg_id = tvg_id_match.group(1) if tvg_id_match else ""
|
||||
|
||||
fallback_name = line.split(",", 1)[-1].strip() if "," in line else "Default Stream"
|
||||
|
||||
name = tvg_name_match.group(1) if tvg_name_match else fallback_name
|
||||
logo_url = tvg_logo_match.group(1) if tvg_logo_match else ""
|
||||
group_title = _get_group_title(line)
|
||||
|
||||
logger.debug(f"Parsed EXTINF: name={name}, logo_url={logo_url}, tvg_id={tvg_id}, group_title={group_title}")
|
||||
extinf = parse_extinf_line(line)
|
||||
if not extinf:
|
||||
continue
|
||||
name = extinf['name']
|
||||
tvg_id = extinf['attributes'].get('tvg-id', '')
|
||||
tvg_logo = extinf['attributes'].get('tvg-logo', '')
|
||||
# Prefer group-title from attributes if available.
|
||||
group_title = extinf['attributes'].get('group-title', _get_group_title(line))
|
||||
logger.debug(f"Parsed EXTINF: name={name}, logo_url={tvg_logo}, tvg_id={tvg_id}, group_title={group_title}")
|
||||
current_info = {
|
||||
"name": name,
|
||||
"logo_url": logo_url,
|
||||
"logo_url": tvg_logo,
|
||||
"group_title": group_title,
|
||||
"tvg_id": tvg_id, # save the tvg-id here
|
||||
"tvg_id": tvg_id,
|
||||
}
|
||||
|
||||
elif current_info and line.startswith('http'):
|
||||
lower_line = line.lower()
|
||||
if any(lower_line.endswith(ext) for ext in skip_exts):
|
||||
|
|
@ -156,7 +179,6 @@ def refresh_single_m3u_account(account_id):
|
|||
current_info = None
|
||||
continue
|
||||
|
||||
# Include tvg_id in the defaults so it gets saved
|
||||
defaults = {
|
||||
"logo_url": current_info["logo_url"],
|
||||
"tvg_id": current_info["tvg_id"]
|
||||
|
|
@ -223,17 +245,13 @@ def parse_m3u_file(file_path, account):
|
|||
for line in lines:
|
||||
line = line.strip()
|
||||
if line.startswith('#EXTINF'):
|
||||
tvg_name_match = re.search(r'tvg-name="([^"]*)"', line)
|
||||
tvg_logo_match = re.search(r'tvg-logo="([^"]*)"', line)
|
||||
fallback_name = line.split(",", 1)[-1].strip() if "," in line else "Stream"
|
||||
tvg_id_match = re.search(r'tvg-id="([^"]*)"', line)
|
||||
tvg_id = tvg_id_match.group(1) if tvg_id_match else ""
|
||||
|
||||
name = tvg_name_match.group(1) if tvg_name_match else fallback_name
|
||||
logo_url = tvg_logo_match.group(1) if tvg_logo_match else ""
|
||||
|
||||
current_info = {"name": name, "logo_url": logo_url, "tvg_id": tvg_id}
|
||||
|
||||
extinf = parse_extinf_line(line)
|
||||
if not extinf:
|
||||
continue
|
||||
name = extinf['name']
|
||||
tvg_id = extinf['attributes'].get('tvg-id', '')
|
||||
tvg_logo = extinf['attributes'].get('tvg-logo', '')
|
||||
current_info = {"name": name, "logo_url": tvg_logo, "tvg_id": tvg_id}
|
||||
elif current_info and line.startswith('http'):
|
||||
lower_line = line.lower()
|
||||
if any(lower_line.endswith(ext) for ext in skip_exts):
|
||||
|
|
|
|||
|
|
@ -69,12 +69,20 @@
|
|||
"value": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "core.coresettings",
|
||||
"fields": {
|
||||
"key": "preferred-region",
|
||||
"name": "Preferred Region",
|
||||
"value": "us"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "core.coresettings",
|
||||
"fields": {
|
||||
"key": "preferred-region",
|
||||
"name": "Preferred Region",
|
||||
"value": "us"
|
||||
"key": "cache-images",
|
||||
"name": "Cache Images",
|
||||
"value": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue