Skip to content

Commit 5fd94bc

Browse files
authored
added popolar tv and movie to tmdb sensors with filtering
1 parent e4bb062 commit 5fd94bc

File tree

4 files changed

+524
-67
lines changed

4 files changed

+524
-67
lines changed

custom_components/mediarr/common/tmdb_sensor.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,30 @@
1111

1212
class TMDBMediaSensor(MediarrSensor, ABC):
1313
"""Base class for TMDB-based media sensors."""
14-
15-
def __init__(self, session, tmdb_api_key, language='en'):
14+
15+
def __init__(self, session, tmdb_api_key, language='en', filters=None):
1616
"""Initialize the sensor."""
1717
super().__init__()
1818
self._session = session
1919
self._tmdb_api_key = tmdb_api_key
2020
self._language = language
2121
self._available = True
2222
self._cache = {}
23+
24+
# Initialize default filters
25+
self._filters = {
26+
'language': language,
27+
'min_year': 0,
28+
'exclude_talk_shows': True,
29+
'exclude_genres': [10763, 10764, 10767], # News, Reality, Talk shows
30+
'exclude_non_english': True
31+
}
32+
33+
# Update with user-provided filters
34+
if filters:
35+
self._filters.update(filters)
36+
37+
2338

2439
# In tmdb_sensor.py, update _format_date method
2540
def _format_date(self, date_str):
@@ -37,6 +52,61 @@ def _format_date(self, date_str):
3752
except Exception:
3853
return 'Unknown'
3954

55+
def is_talk_show(self, title):
56+
"""Check if a show title appears to be a talk show or similar format."""
57+
if not self._filters.get('exclude_talk_shows', True):
58+
return False
59+
60+
keywords = [
61+
'tonight show', 'late show', 'late night', 'daily show',
62+
'talk show', 'with seth meyers', 'with james corden',
63+
'with jimmy', 'with stephen', 'with trevor', 'news',
64+
'live with', 'watch what happens live', 'the view',
65+
'good morning', 'today show', 'kimmel', 'colbert',
66+
'fallon', 'ellen', 'conan', 'graham norton', 'meet the press',
67+
'face the nation', 'last week tonight', 'real time',
68+
'kelly and', 'kelly &', 'jeopardy', 'wheel of fortune',
69+
'daily mail', 'entertainment tonight', 'zeiten', 'schlechte'
70+
]
71+
72+
title_lower = title.lower()
73+
return any(keyword in title_lower for keyword in keywords)
74+
75+
def should_include_item(self, item, media_type):
76+
"""Apply filters to determine if an item should be included."""
77+
# Filter by year
78+
if media_type == 'tv' and 'first_air_date' in item and item['first_air_date']:
79+
try:
80+
year = int(item['first_air_date'].split('-')[0])
81+
if year < self._filters.get('min_year', 0):
82+
return False
83+
except (ValueError, IndexError):
84+
pass
85+
elif media_type == 'movie' and 'release_date' in item and item['release_date']:
86+
try:
87+
year = int(item['release_date'].split('-')[0])
88+
if year < self._filters.get('min_year', 0):
89+
return False
90+
except (ValueError, IndexError):
91+
pass
92+
93+
# Filter by language
94+
if self._filters.get('exclude_non_english', True) and item.get('original_language') != 'en':
95+
return False
96+
97+
# Filter by genre
98+
excluded_genres = self._filters.get('exclude_genres', [])
99+
if any(genre_id in excluded_genres for genre_id in item.get('genre_ids', [])):
100+
return False
101+
102+
# Filter for TV talk shows
103+
if media_type == 'tv':
104+
title = item.get('name', '')
105+
if self.is_talk_show(title):
106+
return False
107+
108+
return True
109+
40110
async def _fetch_tmdb_data(self, endpoint, params=None):
41111
"""Fetch data from TMDB API."""
42112
try:
@@ -90,6 +160,8 @@ async def _fetch_tmdb_data(self, endpoint, params=None):
90160
_LOGGER.error("Error fetching TMDB data: %s", err)
91161
return None
92162

163+
164+
93165
async def _get_tmdb_images(self, tmdb_id, media_type='movie'):
94166
"""Get TMDB image URLs without language filtering."""
95167
if not tmdb_id:

custom_components/mediarr/discovery/seer_discovery.py

Lines changed: 148 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,98 @@
77

88
_LOGGER = logging.getLogger(__name__)
99

10+
# In seer_discovery.py
1011
class SeerDiscoveryMediarrSensor(TMDBMediaSensor):
1112
"""Seer sensor for discover/trending/popular."""
1213

13-
def __init__(self, session, api_key, url, tmdb_api_key, max_items, content_type, media_type=None):
14+
def __init__(self, session, api_key, url, tmdb_api_key, max_items, content_type, media_type=None, filters=None):
1415
"""Initialize the sensor."""
16+
# Initialize TMDBMediaSensor with tmdb_api_key
1517
super().__init__(session, tmdb_api_key)
18+
1619
self._seer_api_key = api_key
1720
self._url = url.rstrip('/')
1821
self._max_items = max_items
1922
self._content_type = content_type
2023
self._media_type = media_type
24+
25+
# Initialize default filters
26+
self._filters = {
27+
'language': 'en',
28+
'min_year': 0,
29+
'exclude_talk_shows': True,
30+
'exclude_genres': [10763, 10764, 10767], # News, Reality, Talk shows
31+
'exclude_non_english': True
32+
}
33+
34+
# Update with user-provided filters
35+
if filters:
36+
self._filters.update(filters)
37+
2138
# Customize name based on content type and media type
2239
if content_type in ["popular_movies", "popular_tv"]:
2340
self._name = f"Seer Mediarr Popular {'Movies' if media_type == 'movies' else 'TV'}"
2441
else:
2542
self._name = f"Seer Mediarr {content_type.title()}"
43+
44+
def should_include_item(self, item, media_type):
45+
"""Apply filters to determine if an item should be included."""
46+
# Skip if no item
47+
if not item:
48+
return False
49+
50+
# Filter by year
51+
year = None
52+
if media_type == 'tv' and item.get('first_air_date'):
53+
try:
54+
year = int(item['first_air_date'].split('-')[0])
55+
except (ValueError, IndexError, TypeError):
56+
pass
57+
elif media_type == 'movie' and item.get('release_date'):
58+
try:
59+
year = int(item['release_date'].split('-')[0])
60+
except (ValueError, IndexError, TypeError):
61+
pass
62+
63+
if year and year < self._filters.get('min_year', 0):
64+
return False
65+
66+
# Filter by language
67+
if self._filters.get('exclude_non_english', True) and item.get('original_language') != 'en':
68+
return False
69+
70+
# Filter by genre
71+
excluded_genres = self._filters.get('exclude_genres', [])
72+
if excluded_genres and any(genre_id in excluded_genres for genre_id in item.get('genre_ids', [])):
73+
return False
74+
75+
# Filter for TV talk shows
76+
if media_type == 'tv' and self._filters.get('exclude_talk_shows', True):
77+
title = item.get('name', '') or item.get('title', '')
78+
if self.is_talk_show(title):
79+
return False
80+
81+
return True
82+
83+
def is_talk_show(self, title):
84+
"""Check if a show title appears to be a talk show or similar format."""
85+
if not self._filters.get('exclude_talk_shows', True) or not title:
86+
return False
87+
88+
keywords = [
89+
'tonight show', 'late show', 'late night', 'daily show',
90+
'talk show', 'with seth meyers', 'with james corden',
91+
'with jimmy', 'with stephen', 'with trevor', 'news',
92+
'live with', 'watch what happens live', 'the view',
93+
'good morning', 'today show', 'kimmel', 'colbert',
94+
'fallon', 'ellen', 'conan', 'graham norton', 'meet the press',
95+
'face the nation', 'last week tonight', 'real time',
96+
'kelly and', 'kelly &', 'jeopardy', 'wheel of fortune',
97+
'daily mail', 'entertainment tonight', 'zeiten', 'schlechte'
98+
]
99+
100+
title_lower = title.lower()
101+
return any(keyword in title_lower for keyword in keywords)
26102

27103
@property
28104
def name(self):
@@ -96,22 +172,45 @@ async def _fetch_all_requests(self):
96172
return set()
97173

98174
async def _process_media_items(self, data, media_type, requested_ids):
99-
"""Process media items in parallel."""
175+
"""Process media items in parallel with filtering."""
100176
if not data or not data.get('results'):
177+
_LOGGER.debug("No data or results to process for %s", media_type)
101178
return []
102179

180+
filtered_count = 0
181+
requested_count = 0
182+
detail_failure_count = 0
183+
success_count = 0
184+
103185
async def process_item(item):
186+
nonlocal filtered_count, requested_count, detail_failure_count, success_count
187+
104188
try:
105189
tmdb_id = str(item.get('id'))
190+
if not tmdb_id:
191+
_LOGGER.debug("Item has no TMDB ID")
192+
return None
193+
106194
if tmdb_id in requested_ids:
195+
requested_count += 1
196+
_LOGGER.debug("Item %s already requested, skipping", tmdb_id)
197+
return None
198+
199+
# Apply filters
200+
if not self.should_include_item(item, media_type):
201+
filtered_count += 1
202+
_LOGGER.debug("Item %s filtered out by criteria", tmdb_id)
107203
return None
108204

109205
details = await self._get_tmdb_details(tmdb_id, media_type)
110206
if not details:
207+
detail_failure_count += 1
208+
_LOGGER.debug("Failed to get TMDB details for %s", tmdb_id)
111209
return None
112210

113211
poster_url, backdrop_url, main_backdrop_url = await self._get_tmdb_images(tmdb_id, media_type)
114212

213+
success_count += 1
115214
return {
116215
'title': details['title'],
117216
'overview': details['overview'][:100] + '...' if details.get('overview') else 'No overview available',
@@ -125,44 +224,87 @@ async def process_item(item):
125224
'id': tmdb_id
126225
}
127226
except Exception as err:
128-
_LOGGER.error("Error processing item %s: %s", tmdb_id, err)
227+
_LOGGER.error("Error processing item %s: %s", tmdb_id if 'tmdb_id' in locals() else 'unknown', err)
129228
return None
130229

131230
# Process items in parallel
231+
_LOGGER.debug("Processing %d items for %s", len(data['results']), media_type)
132232
tasks = [process_item(item) for item in data['results']]
133233
results = await asyncio.gather(*tasks, return_exceptions=True)
134234

135-
# Filter out None values and handle any exceptions
136-
return [item for item in results if item is not None and not isinstance(item, Exception)]
235+
# Handle exceptions
236+
exceptions = [r for r in results if isinstance(r, Exception)]
237+
if exceptions:
238+
_LOGGER.error("Got %d exceptions during processing", len(exceptions))
239+
for exc in exceptions[:3]: # Log first 3 exceptions
240+
_LOGGER.error("Exception: %s", exc)
241+
242+
# Filter out None values and exceptions
243+
processed_results = [item for item in results if item is not None and not isinstance(item, Exception)]
244+
245+
_LOGGER.debug("Processing summary for %s: %d items total, %d already requested, %d filtered out, "
246+
"%d failed to get details, %d successful",
247+
media_type, len(data['results']), requested_count, filtered_count,
248+
detail_failure_count, success_count)
249+
250+
return processed_results
137251

138252
async def async_update(self):
139253
"""Update the sensor."""
140254
try:
141255
# Fetch all current requests first
142256
requested_ids = await self._fetch_all_requests()
257+
_LOGGER.debug("Fetched %d requested IDs from Seer", len(requested_ids))
258+
143259
all_items = []
144260

145261
if self._content_type == "discover":
146262
# Fetch both movies and TV
147263
for media_type in ['movies', 'tv']:
148-
data = await self._fetch_media_list(media_type) # Pass media_type here
264+
_LOGGER.debug("Fetching %s data from Seer for discover", media_type)
265+
data = await self._fetch_media_list(media_type)
266+
267+
if data and 'results' in data:
268+
_LOGGER.debug("Received %d %s items from Seer", len(data['results']), media_type)
269+
# Debug the first item to see its structure
270+
if data['results']:
271+
_LOGGER.debug("Sample item structure: %s", data['results'][0])
272+
else:
273+
_LOGGER.debug("No %s data or no results received from Seer", media_type)
274+
275+
_LOGGER.debug("Processing %s items through filters", media_type)
149276
processed_items = await self._process_media_items(
150277
data,
151278
'movie' if media_type == 'movies' else 'tv',
152279
requested_ids
153280
)
281+
_LOGGER.debug("After filtering: %d %s items remaining", len(processed_items), media_type)
154282
all_items.extend(processed_items)
155283
else:
156284
# Fetch single type (trending, popular movies, or popular TV)
285+
_LOGGER.debug("Fetching %s data from Seer", self._content_type)
157286
data = await self._fetch_media_list()
287+
288+
if data and 'results' in data:
289+
_LOGGER.debug("Received %d items from Seer for %s", len(data['results']), self._content_type)
290+
# Debug the first item to see its structure
291+
if data['results']:
292+
_LOGGER.debug("Sample item structure: %s", data['results'][0])
293+
else:
294+
_LOGGER.debug("No data or no results received from Seer for %s", self._content_type)
295+
158296
media_type = 'movie' if self._content_type == 'popular_movies' else 'tv'
297+
_LOGGER.debug("Processing %s items through filters", self._content_type)
159298
processed_items = await self._process_media_items(data, media_type, requested_ids)
299+
_LOGGER.debug("After filtering: %d items remaining", len(processed_items))
160300
all_items.extend(processed_items)
161301

162302
# Ensure max_items limit is respected
163303
all_items = all_items[:self._max_items]
304+
_LOGGER.debug("Final number of items after max_items limit: %d", len(all_items))
164305

165306
if not all_items:
307+
_LOGGER.warning("No items passed filters for %s, using fallback", self._content_type)
166308
all_items.append({
167309
'title_default': '$title',
168310
'line1_default': '$type',

0 commit comments

Comments
 (0)