mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-23 02:35:14 +00:00
Enhance EPG XML generation with additional metadata extraction and improved handling for keywords, languages, ratings, and credits.
This commit is contained in:
parent
8b6acf2375
commit
d24520d3d8
2 changed files with 293 additions and 30 deletions
|
|
@ -1612,6 +1612,11 @@ def extract_custom_properties(prog):
|
|||
if categories:
|
||||
custom_props['categories'] = categories
|
||||
|
||||
# Extract keywords (new)
|
||||
keywords = [kw.text.strip() for kw in prog.findall('keyword') if kw.text and kw.text.strip()]
|
||||
if keywords:
|
||||
custom_props['keywords'] = keywords
|
||||
|
||||
# Extract episode numbers
|
||||
for ep_num in prog.findall('episode-num'):
|
||||
system = ep_num.get('system', '')
|
||||
|
|
@ -1637,6 +1642,9 @@ def extract_custom_properties(prog):
|
|||
elif system == 'dd_progid' and ep_num.text:
|
||||
# Store the dd_progid format
|
||||
custom_props['dd_progid'] = ep_num.text.strip()
|
||||
# Add support for other systems like thetvdb.com, themoviedb.org, imdb.com
|
||||
elif system in ['thetvdb.com', 'themoviedb.org', 'imdb.com'] and ep_num.text:
|
||||
custom_props[f'{system}_id'] = ep_num.text.strip()
|
||||
|
||||
# Extract ratings more efficiently
|
||||
rating_elem = prog.find('rating')
|
||||
|
|
@ -1647,37 +1655,172 @@ def extract_custom_properties(prog):
|
|||
if rating_elem.get('system'):
|
||||
custom_props['rating_system'] = rating_elem.get('system')
|
||||
|
||||
# Extract star ratings (new)
|
||||
star_ratings = []
|
||||
for star_rating in prog.findall('star-rating'):
|
||||
value_elem = star_rating.find('value')
|
||||
if value_elem is not None and value_elem.text:
|
||||
rating_data = {'value': value_elem.text.strip()}
|
||||
if star_rating.get('system'):
|
||||
rating_data['system'] = star_rating.get('system')
|
||||
star_ratings.append(rating_data)
|
||||
if star_ratings:
|
||||
custom_props['star_ratings'] = star_ratings
|
||||
|
||||
# Extract credits more efficiently
|
||||
credits_elem = prog.find('credits')
|
||||
if credits_elem is not None:
|
||||
credits = {}
|
||||
for credit_type in ['director', 'actor', 'writer', 'presenter', 'producer']:
|
||||
names = [e.text.strip() for e in credits_elem.findall(credit_type) if e.text and e.text.strip()]
|
||||
if names:
|
||||
credits[credit_type] = names
|
||||
for credit_type in ['director', 'actor', 'writer', 'adapter', 'producer', 'composer', 'editor', 'presenter', 'commentator', 'guest']:
|
||||
if credit_type == 'actor':
|
||||
# Handle actors with roles and guest status
|
||||
actors = []
|
||||
for actor_elem in credits_elem.findall('actor'):
|
||||
if actor_elem.text and actor_elem.text.strip():
|
||||
actor_data = {'name': actor_elem.text.strip()}
|
||||
if actor_elem.get('role'):
|
||||
actor_data['role'] = actor_elem.get('role')
|
||||
if actor_elem.get('guest') == 'yes':
|
||||
actor_data['guest'] = True
|
||||
actors.append(actor_data)
|
||||
if actors:
|
||||
credits['actor'] = actors
|
||||
else:
|
||||
names = [e.text.strip() for e in credits_elem.findall(credit_type) if e.text and e.text.strip()]
|
||||
if names:
|
||||
credits[credit_type] = names
|
||||
if credits:
|
||||
custom_props['credits'] = credits
|
||||
|
||||
# Extract other common program metadata
|
||||
date_elem = prog.find('date')
|
||||
if date_elem is not None and date_elem.text:
|
||||
custom_props['year'] = date_elem.text.strip()[:4] # Just the year part
|
||||
custom_props['date'] = date_elem.text.strip()
|
||||
|
||||
country_elem = prog.find('country')
|
||||
if country_elem is not None and country_elem.text:
|
||||
custom_props['country'] = country_elem.text.strip()
|
||||
|
||||
# Extract language information (new)
|
||||
language_elem = prog.find('language')
|
||||
if language_elem is not None and language_elem.text:
|
||||
custom_props['language'] = language_elem.text.strip()
|
||||
|
||||
orig_language_elem = prog.find('orig-language')
|
||||
if orig_language_elem is not None and orig_language_elem.text:
|
||||
custom_props['original_language'] = orig_language_elem.text.strip()
|
||||
|
||||
# Extract length (new)
|
||||
length_elem = prog.find('length')
|
||||
if length_elem is not None and length_elem.text:
|
||||
try:
|
||||
length_value = int(length_elem.text.strip())
|
||||
length_units = length_elem.get('units', 'minutes')
|
||||
custom_props['length'] = {'value': length_value, 'units': length_units}
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Extract video information (new)
|
||||
video_elem = prog.find('video')
|
||||
if video_elem is not None:
|
||||
video_info = {}
|
||||
for video_attr in ['present', 'colour', 'aspect', 'quality']:
|
||||
attr_elem = video_elem.find(video_attr)
|
||||
if attr_elem is not None and attr_elem.text:
|
||||
video_info[video_attr] = attr_elem.text.strip()
|
||||
if video_info:
|
||||
custom_props['video'] = video_info
|
||||
|
||||
# Extract audio information (new)
|
||||
audio_elem = prog.find('audio')
|
||||
if audio_elem is not None:
|
||||
audio_info = {}
|
||||
for audio_attr in ['present', 'stereo']:
|
||||
attr_elem = audio_elem.find(audio_attr)
|
||||
if attr_elem is not None and attr_elem.text:
|
||||
audio_info[audio_attr] = attr_elem.text.strip()
|
||||
if audio_info:
|
||||
custom_props['audio'] = audio_info
|
||||
|
||||
# Extract subtitles information (new)
|
||||
subtitles = []
|
||||
for subtitle_elem in prog.findall('subtitles'):
|
||||
subtitle_data = {}
|
||||
if subtitle_elem.get('type'):
|
||||
subtitle_data['type'] = subtitle_elem.get('type')
|
||||
lang_elem = subtitle_elem.find('language')
|
||||
if lang_elem is not None and lang_elem.text:
|
||||
subtitle_data['language'] = lang_elem.text.strip()
|
||||
if subtitle_data:
|
||||
subtitles.append(subtitle_data)
|
||||
|
||||
if subtitles:
|
||||
custom_props['subtitles'] = subtitles
|
||||
|
||||
# Extract reviews (new)
|
||||
reviews = []
|
||||
for review_elem in prog.findall('review'):
|
||||
if review_elem.text and review_elem.text.strip():
|
||||
review_data = {'content': review_elem.text.strip()}
|
||||
if review_elem.get('type'):
|
||||
review_data['type'] = review_elem.get('type')
|
||||
if review_elem.get('source'):
|
||||
review_data['source'] = review_elem.get('source')
|
||||
if review_elem.get('reviewer'):
|
||||
review_data['reviewer'] = review_elem.get('reviewer')
|
||||
reviews.append(review_data)
|
||||
if reviews:
|
||||
custom_props['reviews'] = reviews
|
||||
|
||||
# Extract images (new)
|
||||
images = []
|
||||
for image_elem in prog.findall('image'):
|
||||
if image_elem.text and image_elem.text.strip():
|
||||
image_data = {'url': image_elem.text.strip()}
|
||||
for attr in ['type', 'size', 'orient', 'system']:
|
||||
if image_elem.get(attr):
|
||||
image_data[attr] = image_elem.get(attr)
|
||||
images.append(image_data)
|
||||
if images:
|
||||
custom_props['images'] = images
|
||||
|
||||
icon_elem = prog.find('icon')
|
||||
if icon_elem is not None and icon_elem.get('src'):
|
||||
custom_props['icon'] = icon_elem.get('src')
|
||||
|
||||
# Simpler approach for boolean flags
|
||||
for kw in ['previously-shown', 'premiere', 'new', 'live']:
|
||||
# Simpler approach for boolean flags - expanded list
|
||||
for kw in ['previously-shown', 'premiere', 'new', 'live', 'last-chance']:
|
||||
if prog.find(kw) is not None:
|
||||
custom_props[kw.replace('-', '_')] = True
|
||||
|
||||
# Extract premiere and last-chance text content if available
|
||||
premiere_elem = prog.find('premiere')
|
||||
if premiere_elem is not None:
|
||||
custom_props['premiere'] = True
|
||||
if premiere_elem.text and premiere_elem.text.strip():
|
||||
custom_props['premiere_text'] = premiere_elem.text.strip()
|
||||
|
||||
last_chance_elem = prog.find('last-chance')
|
||||
if last_chance_elem is not None:
|
||||
custom_props['last_chance'] = True
|
||||
if last_chance_elem.text and last_chance_elem.text.strip():
|
||||
custom_props['last_chance_text'] = last_chance_elem.text.strip()
|
||||
|
||||
# Extract previously-shown details
|
||||
prev_shown_elem = prog.find('previously-shown')
|
||||
if prev_shown_elem is not None:
|
||||
custom_props['previously_shown'] = True
|
||||
prev_shown_data = {}
|
||||
if prev_shown_elem.get('start'):
|
||||
prev_shown_data['start'] = prev_shown_elem.get('start')
|
||||
if prev_shown_elem.get('channel'):
|
||||
prev_shown_data['channel'] = prev_shown_elem.get('channel')
|
||||
if prev_shown_data:
|
||||
custom_props['previously_shown_details'] = prev_shown_data
|
||||
|
||||
return custom_props
|
||||
|
||||
|
||||
def clear_element(elem):
|
||||
"""Clear an XML element and its parent to free memory."""
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -467,19 +467,27 @@ def generate_epg(request, profile_name=None, user=None):
|
|||
for category in custom_data["categories"]:
|
||||
program_xml.append(f" <category>{html.escape(category)}</category>")
|
||||
|
||||
# Handle episode numbering - multiple formats supported
|
||||
# Standard episode number if available
|
||||
if "episode" in custom_data:
|
||||
program_xml.append(f' <episode-num system="onscreen">E{custom_data["episode"]}</episode-num>')
|
||||
# Add keywords if available
|
||||
if "keywords" in custom_data and custom_data["keywords"]:
|
||||
for keyword in custom_data["keywords"]:
|
||||
program_xml.append(f" <keyword>{html.escape(keyword)}</keyword>")
|
||||
|
||||
# Handle onscreen episode format (like S06E128)
|
||||
# Handle episode numbering - multiple formats supported
|
||||
# Prioritize onscreen_episode over standalone episode for onscreen system
|
||||
if "onscreen_episode" in custom_data:
|
||||
program_xml.append(f' <episode-num system="onscreen">{html.escape(custom_data["onscreen_episode"])}</episode-num>')
|
||||
elif "episode" in custom_data:
|
||||
program_xml.append(f' <episode-num system="onscreen">E{custom_data["episode"]}</episode-num>')
|
||||
|
||||
# Handle dd_progid format
|
||||
if 'dd_progid' in custom_data:
|
||||
program_xml.append(f' <episode-num system="dd_progid">{html.escape(custom_data["dd_progid"])}</episode-num>')
|
||||
|
||||
# Handle external database IDs
|
||||
for system in ['thetvdb.com', 'themoviedb.org', 'imdb.com']:
|
||||
if f'{system}_id' in custom_data:
|
||||
program_xml.append(f' <episode-num system="{system}">{html.escape(custom_data[f"{system}_id"])}</episode-num>')
|
||||
|
||||
# Add season and episode numbers in xmltv_ns format if available
|
||||
if "season" in custom_data and "episode" in custom_data:
|
||||
season = (
|
||||
|
|
@ -494,6 +502,46 @@ def generate_epg(request, profile_name=None, user=None):
|
|||
)
|
||||
program_xml.append(f' <episode-num system="xmltv_ns">{season}.{episode}.</episode-num>')
|
||||
|
||||
# Add language information
|
||||
if "language" in custom_data:
|
||||
program_xml.append(f' <language>{html.escape(custom_data["language"])}</language>')
|
||||
|
||||
if "original_language" in custom_data:
|
||||
program_xml.append(f' <orig-language>{html.escape(custom_data["original_language"])}</orig-language>')
|
||||
|
||||
# Add length information
|
||||
if "length" in custom_data and isinstance(custom_data["length"], dict):
|
||||
length_value = custom_data["length"].get("value", "")
|
||||
length_units = custom_data["length"].get("units", "minutes")
|
||||
program_xml.append(f' <length units="{html.escape(length_units)}">{html.escape(str(length_value))}</length>')
|
||||
|
||||
# Add video information
|
||||
if "video" in custom_data and isinstance(custom_data["video"], dict):
|
||||
program_xml.append(" <video>")
|
||||
for attr in ['present', 'colour', 'aspect', 'quality']:
|
||||
if attr in custom_data["video"]:
|
||||
program_xml.append(f" <{attr}>{html.escape(custom_data['video'][attr])}</{attr}>")
|
||||
program_xml.append(" </video>")
|
||||
|
||||
# Add audio information
|
||||
if "audio" in custom_data and isinstance(custom_data["audio"], dict):
|
||||
program_xml.append(" <audio>")
|
||||
for attr in ['present', 'stereo']:
|
||||
if attr in custom_data["audio"]:
|
||||
program_xml.append(f" <{attr}>{html.escape(custom_data['audio'][attr])}</{attr}>")
|
||||
program_xml.append(" </audio>")
|
||||
|
||||
# Add subtitles information
|
||||
if "subtitles" in custom_data and isinstance(custom_data["subtitles"], list):
|
||||
for subtitle in custom_data["subtitles"]:
|
||||
if isinstance(subtitle, dict):
|
||||
subtitle_type = subtitle.get("type", "")
|
||||
type_attr = f' type="{html.escape(subtitle_type)}"' if subtitle_type else ""
|
||||
program_xml.append(f" <subtitles{type_attr}>")
|
||||
if "language" in subtitle:
|
||||
program_xml.append(f" <language>{html.escape(subtitle['language'])}</language>")
|
||||
program_xml.append(" </subtitles>")
|
||||
|
||||
# Add rating if available
|
||||
if "rating" in custom_data:
|
||||
rating_system = custom_data.get("rating_system", "TV Parental Guidelines")
|
||||
|
|
@ -501,20 +549,74 @@ def generate_epg(request, profile_name=None, user=None):
|
|||
program_xml.append(f' <value>{html.escape(custom_data["rating"])}</value>')
|
||||
program_xml.append(f" </rating>")
|
||||
|
||||
# Add actors/directors/writers if available
|
||||
if "credits" in custom_data:
|
||||
program_xml.append(f" <credits>")
|
||||
for role, people in custom_data["credits"].items():
|
||||
if isinstance(people, list):
|
||||
for person in people:
|
||||
program_xml.append(f" <{role}>{html.escape(person)}</{role}>")
|
||||
else:
|
||||
program_xml.append(f" <{role}>{html.escape(people)}</{role}>")
|
||||
program_xml.append(f" </credits>")
|
||||
# Add star ratings
|
||||
if "star_ratings" in custom_data and isinstance(custom_data["star_ratings"], list):
|
||||
for star_rating in custom_data["star_ratings"]:
|
||||
if isinstance(star_rating, dict) and "value" in star_rating:
|
||||
system_attr = f' system="{html.escape(star_rating["system"])}"' if "system" in star_rating else ""
|
||||
program_xml.append(f" <star-rating{system_attr}>")
|
||||
program_xml.append(f" <value>{html.escape(star_rating['value'])}</value>")
|
||||
program_xml.append(" </star-rating>")
|
||||
|
||||
# Add program date/year if available
|
||||
if "year" in custom_data:
|
||||
program_xml.append(f' <date>{html.escape(custom_data["year"])}</date>')
|
||||
# Add reviews
|
||||
if "reviews" in custom_data and isinstance(custom_data["reviews"], list):
|
||||
for review in custom_data["reviews"]:
|
||||
if isinstance(review, dict) and "content" in review:
|
||||
review_type = review.get("type", "text")
|
||||
attrs = [f'type="{html.escape(review_type)}"']
|
||||
if "source" in review:
|
||||
attrs.append(f'source="{html.escape(review["source"])}"')
|
||||
if "reviewer" in review:
|
||||
attrs.append(f'reviewer="{html.escape(review["reviewer"])}"')
|
||||
attr_str = " ".join(attrs)
|
||||
program_xml.append(f' <review {attr_str}>{html.escape(review["content"])}</review>')
|
||||
|
||||
# Add images
|
||||
if "images" in custom_data and isinstance(custom_data["images"], list):
|
||||
for image in custom_data["images"]:
|
||||
if isinstance(image, dict) and "url" in image:
|
||||
attrs = []
|
||||
for attr in ['type', 'size', 'orient', 'system']:
|
||||
if attr in image:
|
||||
attrs.append(f'{attr}="{html.escape(image[attr])}"')
|
||||
attr_str = " " + " ".join(attrs) if attrs else ""
|
||||
program_xml.append(f' <image{attr_str}>{html.escape(image["url"])}</image>')
|
||||
|
||||
# Add enhanced credits handling
|
||||
if "credits" in custom_data:
|
||||
program_xml.append(" <credits>")
|
||||
credits = custom_data["credits"]
|
||||
|
||||
# Handle different credit types
|
||||
for role in ['director', 'writer', 'adapter', 'producer', 'composer', 'editor', 'presenter', 'commentator', 'guest']:
|
||||
if role in credits:
|
||||
people = credits[role]
|
||||
if isinstance(people, list):
|
||||
for person in people:
|
||||
program_xml.append(f" <{role}>{html.escape(person)}</{role}>")
|
||||
else:
|
||||
program_xml.append(f" <{role}>{html.escape(people)}</{role}>")
|
||||
|
||||
# Handle actors separately to include role and guest attributes
|
||||
if "actor" in credits:
|
||||
actors = credits["actor"]
|
||||
if isinstance(actors, list):
|
||||
for actor in actors:
|
||||
if isinstance(actor, dict):
|
||||
name = actor.get("name", "")
|
||||
role_attr = f' role="{html.escape(actor["role"])}"' if "role" in actor else ""
|
||||
guest_attr = ' guest="yes"' if actor.get("guest") else ""
|
||||
program_xml.append(f" <actor{role_attr}{guest_attr}>{html.escape(name)}</actor>")
|
||||
else:
|
||||
program_xml.append(f" <actor>{html.escape(actor)}</actor>")
|
||||
else:
|
||||
program_xml.append(f" <actor>{html.escape(actors)}</actor>")
|
||||
|
||||
program_xml.append(" </credits>")
|
||||
|
||||
# Add program date if available (full date, not just year)
|
||||
if "date" in custom_data:
|
||||
program_xml.append(f' <date>{html.escape(custom_data["date"])}</date>')
|
||||
|
||||
# Add country if available
|
||||
if "country" in custom_data:
|
||||
|
|
@ -524,18 +626,36 @@ def generate_epg(request, profile_name=None, user=None):
|
|||
if "icon" in custom_data:
|
||||
program_xml.append(f' <icon src="{html.escape(custom_data["icon"])}" />')
|
||||
|
||||
# Add special flags as proper tags
|
||||
# Add special flags as proper tags with enhanced handling
|
||||
if custom_data.get("previously_shown", False):
|
||||
program_xml.append(f" <previously-shown />")
|
||||
prev_shown_details = custom_data.get("previously_shown_details", {})
|
||||
attrs = []
|
||||
if "start" in prev_shown_details:
|
||||
attrs.append(f'start="{html.escape(prev_shown_details["start"])}"')
|
||||
if "channel" in prev_shown_details:
|
||||
attrs.append(f'channel="{html.escape(prev_shown_details["channel"])}"')
|
||||
attr_str = " " + " ".join(attrs) if attrs else ""
|
||||
program_xml.append(f" <previously-shown{attr_str} />")
|
||||
|
||||
if custom_data.get("premiere", False):
|
||||
program_xml.append(f" <premiere />")
|
||||
premiere_text = custom_data.get("premiere_text", "")
|
||||
if premiere_text:
|
||||
program_xml.append(f" <premiere>{html.escape(premiere_text)}</premiere>")
|
||||
else:
|
||||
program_xml.append(" <premiere />")
|
||||
|
||||
if custom_data.get("last_chance", False):
|
||||
last_chance_text = custom_data.get("last_chance_text", "")
|
||||
if last_chance_text:
|
||||
program_xml.append(f" <last-chance>{html.escape(last_chance_text)}</last-chance>")
|
||||
else:
|
||||
program_xml.append(" <last-chance />")
|
||||
|
||||
if custom_data.get("new", False):
|
||||
program_xml.append(f" <new />")
|
||||
program_xml.append(" <new />")
|
||||
|
||||
if custom_data.get('live', False):
|
||||
program_xml.append(f' <live />')
|
||||
program_xml.append(' <live />')
|
||||
|
||||
except Exception as e:
|
||||
program_xml.append(f" <!-- Error parsing custom properties: {html.escape(str(e))} -->")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue