Skip to content

Commit 1dfb9df

Browse files
authored
fixed plex updating issue
1 parent e111db5 commit 1dfb9df

File tree

3 files changed

+221
-69
lines changed

3 files changed

+221
-69
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""Trakt user collections integration for Mediarr."""
2+
import logging
3+
from datetime import datetime, timedelta
4+
from ..common.sensor import MediarrSensor
5+
6+
_LOGGER = logging.getLogger(__name__)
7+
8+
TRAKT_USER_ENDPOINTS = {
9+
'collection': ['collection/movies', 'collection/shows'],
10+
'watched': ['watched/movies', 'watched/shows'],
11+
'watchlist': ['watchlist/movies', 'watchlist/shows'],
12+
'recommendations': ['recommendations/movies', 'recommendations/shows'],
13+
'recently_watched': ['history/movies', 'history/shows'],
14+
'upcoming': ['calendar/movies', 'calendar/my/shows']
15+
}
16+
17+
class TraktUserMediarrSensor(MediarrSensor):
18+
"""Representation of a Trakt user collection sensor."""
19+
20+
def __init__(self, session, username, client_id, client_secret, endpoint, max_items, tmdb_api_key=None):
21+
"""Initialize the sensor."""
22+
super().__init__()
23+
self._session = session
24+
self._username = username
25+
self._client_id = client_id
26+
self._client_secret = client_secret
27+
self._endpoint = endpoint
28+
self._max_items = max_items
29+
self._tmdb_api_key = tmdb_api_key
30+
self._name = f"Trakt User Mediarr {endpoint.replace('_', ' ').title()}"
31+
self._access_token = None
32+
self._headers = {
33+
'Content-Type': 'application/json',
34+
'trakt-api-version': '2',
35+
'trakt-api-key': client_id
36+
}
37+
38+
@property
39+
def name(self):
40+
"""Return the name of the sensor."""
41+
return self._name
42+
43+
@property
44+
def unique_id(self):
45+
"""Return the unique ID of the sensor."""
46+
return f"trakt_user_mediarr_{self._endpoint}"
47+
48+
async def async_update(self):
49+
"""Update sensor state."""
50+
try:
51+
results = []
52+
endpoints = TRAKT_USER_ENDPOINTS.get(self._endpoint, [])
53+
54+
for endpoint in endpoints:
55+
params = {}
56+
if self._endpoint == 'upcoming':
57+
start_date = datetime.now().strftime('%Y-%m-%d')
58+
end_date = (datetime.now() + timedelta(days=30)).strftime('%Y-%m-%d')
59+
params = {'start_date': start_date, 'end_date': end_date}
60+
elif self._endpoint == 'recently_watched':
61+
params = {'limit': self._max_items}
62+
63+
async with self._session.get(
64+
f"https://api.trakt.tv/users/{self._username}/{endpoint}",
65+
headers=self._headers,
66+
params=params
67+
) as response:
68+
if response.status == 200:
69+
data = await response.json()
70+
71+
for item in data:
72+
if 'movie' in item:
73+
media_item = item['movie']
74+
media_type = 'movie'
75+
elif 'show' in item:
76+
media_item = item['show']
77+
media_type = 'show'
78+
else:
79+
continue
80+
81+
result = {
82+
'title': media_item.get('title'),
83+
'year': media_item.get('year'),
84+
'type': media_type,
85+
'collected_at': item.get('collected_at'),
86+
'last_watched_at': item.get('last_watched_at'),
87+
'updated_at': item.get('updated_at'),
88+
'ids': media_item.get('ids', {}),
89+
'tmdb_id': media_item.get('ids', {}).get('tmdb'),
90+
'imdb_id': media_item.get('ids', {}).get('imdb'),
91+
'trakt_id': media_item.get('ids', {}).get('trakt')
92+
}
93+
94+
if self._tmdb_api_key and result['tmdb_id']:
95+
tmdb_data = await self._fetch_tmdb_data(
96+
result['tmdb_id'],
97+
'movie' if media_type == 'movie' else 'tv'
98+
)
99+
result.update(tmdb_data)
100+
101+
results.append(result)
102+
103+
self._state = len(results)
104+
self._attributes = {'data': results[:self._max_items]}
105+
self._available = True
106+
107+
except Exception as err:
108+
_LOGGER.error("Error updating Trakt user sensor: %s", err)
109+
self._state = None
110+
self._attributes = {'data': []}
111+
self._available = False
112+
113+
async def _fetch_tmdb_data(self, tmdb_id, media_type):
114+
"""Fetch additional metadata from TMDB."""
115+
try:
116+
headers = {
117+
'Authorization': f'Bearer {self._tmdb_api_key}',
118+
'accept': 'application/json'
119+
}
120+
121+
async with self._session.get(
122+
f"https://api.themoviedb.org/3/{media_type}/{tmdb_id}",
123+
headers=headers
124+
) as response:
125+
if response.status == 200:
126+
data = await response.json()
127+
return {
128+
'poster': f"https://image.tmdb.org/t/p/w500{data.get('poster_path')}" if data.get('poster_path') else None,
129+
'backdrop': f"https://image.tmdb.org/t/p/original{data.get('backdrop_path')}" if data.get('backdrop_path') else None,
130+
'overview': data.get('overview'),
131+
'vote_average': data.get('vote_average'),
132+
'popularity': data.get('popularity')
133+
}
134+
return {}
135+
except Exception as err:
136+
_LOGGER.error("Error fetching TMDB data: %s", err)
137+
return {}

custom_components/mediarr/server/jellyfin.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,6 @@ def unique_id(self):
5050

5151
async def _download_and_cache_image(self, url, item_id, image_type):
5252
"""Download and cache an image from Jellyfin."""
53-
cache_dir = Path(self.hass.config.path("www/mediarr/cache"))
54-
cache_dir.mkdir(parents=True, exist_ok=True)
55-
56-
file_name = f"{item_id}_{image_type}.jpg"
57-
cached_path = cache_dir / file_name
58-
5953
try:
6054
headers = {
6155
"Authorization": f'MediaBrowser Token="{self._jellyfin_token}"',
@@ -65,12 +59,21 @@ async def _download_and_cache_image(self, url, item_id, image_type):
6559
async with async_timeout.timeout(10):
6660
async with self._session.get(url, headers=headers) as response:
6761
if response.status == 200:
62+
cache_dir = Path(self.hass.config.path("www/mediarr/cache"))
63+
cache_dir.mkdir(parents=True, exist_ok=True)
64+
65+
file_name = f"{item_id}_{image_type}.jpg"
66+
cached_path = cache_dir / file_name
67+
6868
content = await response.read()
6969
with open(cached_path, 'wb') as f:
7070
f.write(content)
71+
_LOGGER.debug("Successfully cached image for %s: %s", item_id, image_type)
7172
return f"/local/mediarr/cache/{file_name}"
73+
else:
74+
_LOGGER.warning("Failed to download image %s for %s: %s", image_type, item_id, response.status)
7275
except Exception as err:
73-
_LOGGER.error("Error caching image: %s", err)
76+
_LOGGER.error("Error caching image %s for %s: %s", image_type, item_id, err)
7477
return None
7578

7679
def _clean_unused_images(self, current_ids):
@@ -83,7 +86,11 @@ def _clean_unused_images(self, current_ids):
8386
for image_file in cache_dir.glob("*.jpg"):
8487
item_id = image_file.stem.split('_')[0]
8588
if item_id not in current_ids:
86-
image_file.unlink(missing_ok=True)
89+
try:
90+
image_file.unlink()
91+
_LOGGER.debug("Removed unused image: %s", image_file.name)
92+
except Exception as err:
93+
_LOGGER.error("Error removing image %s: %s", image_file.name, err)
8794
except Exception as err:
8895
_LOGGER.error("Error cleaning cached images: %s", err)
8996

@@ -97,6 +104,8 @@ async def _get_jellyfin_images(self, item_id):
97104
cached_poster = await self._download_and_cache_image(poster_url, item_id, "poster")
98105
cached_backdrop = await self._download_and_cache_image(backdrop_url, item_id, "backdrop")
99106

107+
if cached_poster or cached_backdrop:
108+
_LOGGER.debug("Successfully cached images for item %s", item_id)
100109
return cached_poster, cached_backdrop, cached_backdrop
101110
except Exception as err:
102111
_LOGGER.error("Error getting Jellyfin images: %s", err)
@@ -168,10 +177,13 @@ async def _process_item(self, item):
168177
clean_title = series_name.split('(')[0].strip()
169178
tmdb_id = await self._search_tmdb(clean_title, None, 'tv')
170179

171-
# Try TMDB images first, fall back to Jellyfin images
180+
# Try TMDB images first
181+
poster_url = backdrop_url = main_backdrop_url = None
172182
if tmdb_id:
173183
poster_url, backdrop_url, main_backdrop_url = await self._get_tmdb_images(tmdb_id, 'tv')
174-
if not tmdb_id or not (poster_url or backdrop_url or main_backdrop_url):
184+
185+
# Fallback to Jellyfin images if needed
186+
if not (poster_url and backdrop_url and main_backdrop_url):
175187
poster_url, backdrop_url, main_backdrop_url = await self._get_jellyfin_images(item_id)
176188

177189
return {
@@ -199,10 +211,13 @@ async def _process_item(self, item):
199211
clean_title = title.split('(')[0].strip()
200212
tmdb_id = await self._search_tmdb(clean_title, None, 'movie')
201213

202-
# Try TMDB images first, fall back to Jellyfin images
214+
# Try TMDB images first
215+
poster_url = backdrop_url = main_backdrop_url = None
203216
if tmdb_id:
204217
poster_url, backdrop_url, main_backdrop_url = await self._get_tmdb_images(tmdb_id, 'movie')
205-
if not tmdb_id or not (poster_url or backdrop_url or main_backdrop_url):
218+
219+
# Fallback to Jellyfin images if needed
220+
if not (poster_url and backdrop_url and main_backdrop_url):
206221
poster_url, backdrop_url, main_backdrop_url = await self._get_jellyfin_images(item_id)
207222

208223
return {

0 commit comments

Comments
 (0)