Skip to content

Commit 4f89180

Browse files
authored
Merge pull request #91 from Makhuta/master
Fix
2 parents 43bf9ed + e36b64b commit 4f89180

File tree

3 files changed

+68
-29
lines changed

3 files changed

+68
-29
lines changed

custom_components/plex_recently_added/parser.py

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
from xml.etree import ElementTree
22
import os
3+
import hashlib
4+
from urllib.parse import urlparse
5+
import aiohttp
6+
import aiofiles
37
import re
48
import math
59
from datetime import datetime
610

11+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
12+
CACHE_FOLDER = os.path.join(SCRIPT_DIR, "images_cache")
13+
os.makedirs(CACHE_FOLDER, exist_ok=True)
14+
15+
import logging
16+
_LOGGER = logging.getLogger(__name__)
17+
718
def parse_library(root):
819
output = []
920
for medium in root.findall("Video"):
@@ -19,19 +30,34 @@ def parse_library(root):
1930

2031
return output
2132

22-
def extract_metadata_and_type(path):
23-
pattern = re.compile(r"/library/metadata/(\d+)/(thumb|art)/(\d+)")
24-
match = pattern.search(path)
25-
26-
if match:
27-
metadata_id = match.group(1)
28-
art_type = match.group(2)
29-
art_id = match.group(3)
30-
return metadata_id, art_type, art_id
31-
33+
def get_image_filename(url):
34+
"""Generate a unique filename from the URL while keeping the original extension."""
35+
parsed_url = urlparse(url)
36+
ext = os.path.splitext(parsed_url.path)[-1]
37+
if not ext:
38+
ext = ".jpg"
39+
return hashlib.md5(url.encode()).hexdigest() + ext
40+
41+
async def download_image(url):
42+
"""Download an image asynchronously and save it to the cache folder without blocking the event loop."""
43+
filename = get_image_filename(url)
44+
file_path = os.path.join(CACHE_FOLDER, filename)
45+
46+
async with aiohttp.ClientSession() as session:
47+
async with session.get(url) as response:
48+
if response.status == 200:
49+
async with aiofiles.open(file_path, "wb") as file: # ✅ Non-blocking file write
50+
await file.write(await response.read()) # ✅ Async file writing
51+
return filename
3252
return None
3353

34-
def parse_data(data, max, base_url, token, identifier, section_key, images_base_url, is_all = False):
54+
def cleanup_old_images(valid_filenames):
55+
"""Delete images that are not in the updated list."""
56+
for filename in os.listdir(CACHE_FOLDER):
57+
if filename not in valid_filenames:
58+
os.remove(os.path.join(CACHE_FOLDER, filename))
59+
60+
async def parse_data(data, max, base_url, token, identifier, section_key, images_base_url, is_all = False):
3561
if is_all:
3662
sorted_data = []
3763
for k in data.keys():
@@ -42,6 +68,7 @@ def parse_data(data, max, base_url, token, identifier, section_key, images_base_
4268
sorted_data = sorted(data, key=lambda i: i['addedAt'], reverse=True)[:max]
4369

4470
output = []
71+
valid_images = set()
4572
for item in sorted_data:
4673
media_type_map = {'movie': ('thumb', 'art'), 'episode': ('grandparentThumb', 'grandparentArt')}
4774
thumb_key, art_key = media_type_map.get(item['type'], ('thumb', 'grandparentArt'))
@@ -80,10 +107,20 @@ def parse_data(data, max, base_url, token, identifier, section_key, images_base_
80107
data_output["rating"] = ('\N{BLACK STAR} ' + str(item.get("rating"))) if int(float(item.get("rating", 0))) > 0 else ''
81108
data_output['summary'] = item.get('summary', '')
82109
data_output['trailer'] = item.get('trailer')
83-
thumb_IDs = extract_metadata_and_type(thumb)
84-
data_output["poster"] = (f'{images_base_url}?metadata={thumb_IDs[0]}&thumb={thumb_IDs[2]}') if thumb_IDs else ""
85-
art_IDs = extract_metadata_and_type(art)
86-
data_output["fanart"] = (f'{images_base_url}?metadata={art_IDs[0]}&art={art_IDs[2]}') if art_IDs else ""
110+
111+
112+
thumb_filename = await download_image(f'{base_url}{thumb}?X-Plex-Token={token}')
113+
if thumb_filename:
114+
valid_images.add(thumb_filename)
115+
data_output["poster"] = (f'{images_base_url}?filename={thumb_filename}') if thumb_filename else ""
116+
117+
118+
art_filename = await download_image(f'{base_url}{art}?X-Plex-Token={token}')
119+
if art_filename:
120+
valid_images.add(art_filename)
121+
data_output["fanart"] = (f'{images_base_url}?filename={art_filename}') if art_filename else ""
122+
123+
87124
data_output["deep_link"] = deep_link if identifier else None
88125

89126
output.append(data_output)

custom_components/plex_recently_added/plex_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ async def update(self):
129129

130130
data_out = {}
131131
for k in data.keys():
132-
parsed_data = parse_data(data[k], self._max, info_url, self._token, identifier, k, self._images_base_url, k == "all")
132+
parsed_data = await parse_data(data[k], self._max, info_url, self._token, identifier, k, self._images_base_url, k == "all")
133133

134134
# Ensure trailer URLs are correctly set for the "all" sensor
135135
if k == "all":

custom_components/plex_recently_added/redirect.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
from homeassistant.config_entries import ConfigEntry
33
from aiohttp import web, ClientSession
44
import requests
5+
import os
6+
7+
from .parser import CACHE_FOLDER
8+
os.makedirs(CACHE_FOLDER, exist_ok=True)
59

610
from homeassistant.const import (
711
CONF_API_KEY,
@@ -19,20 +23,18 @@ class ImagesRedirect(HomeAssistantView):
1923
def __init__(self, config_entry: ConfigEntry):
2024
super().__init__()
2125
self._token = config_entry.data[CONF_API_KEY]
22-
self._base_url = f'http{'s' if config_entry.data[CONF_SSL] else ''}://{config_entry.data[CONF_HOST]}:{config_entry.data[CONF_PORT]}'
2326
self.name = f'{self._token}_Plex_Recently_Added'
2427
self.url = f'/{config_entry.data[CONF_NAME].lower() + "_" if len(config_entry.data[CONF_NAME]) > 0 else ""}plex_recently_added'
2528

2629
async def get(self, request):
27-
metadataId = int(request.query.get("metadata", 0))
28-
thumbId = int(request.query.get("thumb", 0))
29-
artId = int(request.query.get("art", 0))
30-
url = f'{self._base_url}/library/metadata/{metadataId}/{"thumb" if thumbId != 0 else "art"}/{thumbId if thumbId != 0 else artId if artId != 0 else ""}?X-Plex-Token={self._token}'
31-
32-
async with ClientSession() as session:
33-
async with session.get(url) as res:
34-
if res.ok:
35-
content = await res.read()
36-
return web.Response(body=content, content_type=res.content_type)
37-
38-
return web.HTTPNotFound()
30+
filename = request.query.get("filename")
31+
if not filename:
32+
return web.Response(status=404)
33+
file_path = os.path.join(CACHE_FOLDER, filename)
34+
35+
if not os.path.isfile(file_path):
36+
return web.Response(status=404)
37+
38+
return web.FileResponse(file_path)
39+
40+

0 commit comments

Comments
 (0)