mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-30 20:05:42 +08:00
fix(emby): include image tags in primary image URLs
This commit is contained in:
@@ -22,6 +22,8 @@ from app.schemas.types import MediaSource, MediaType
|
||||
|
||||
|
||||
class Emby:
|
||||
"""Emby媒体服务器客户端。"""
|
||||
|
||||
_host: Optional[str] = None
|
||||
_playhost: Optional[str] = None
|
||||
_apikey: Optional[str] = None
|
||||
@@ -175,7 +177,10 @@ class Emby:
|
||||
library_type = MediaType.TV.value
|
||||
else:
|
||||
library_type = MediaType.UNKNOWN.value
|
||||
image = self.__get_local_image_by_id(library.get("Id"))
|
||||
image = self.__get_local_image_by_id(
|
||||
library.get("Id"),
|
||||
image_tag=(library.get("ImageTags") or {}).get("Primary"),
|
||||
)
|
||||
server_id = library.get("ServerId") or self.serverid
|
||||
server_query = f"serverId={server_id}&" if server_id else ""
|
||||
libraries.append(
|
||||
@@ -1251,16 +1256,16 @@ class Emby:
|
||||
return f"{host_url}Items/{item_id}/" \
|
||||
f"Images/Backdrop?tag={image_tag}&api_key={self._apikey}"
|
||||
|
||||
def __get_local_image_by_id(self, item_id: str) -> str:
|
||||
def __get_local_image_by_id(self, item_id: str, image_tag: Optional[str] = None) -> str:
|
||||
"""
|
||||
根据ItemId从媒体服务器查询本地图片地址
|
||||
:param: item_id: 在Emby中的ID
|
||||
:param: remote 是否远程使用,TG微信等客户端调用应为True
|
||||
:param: inner 是否NT内部调用,为True是会使用NT中转
|
||||
:param: image_tag: Emby图片版本标签,用于使图片缓存随封面更新失效
|
||||
"""
|
||||
if not self._host or not self._apikey:
|
||||
return ""
|
||||
return "%sItems/%s/Images/Primary" % (self._host, item_id)
|
||||
image_url = f"{self._host}Items/{item_id}/Images/Primary"
|
||||
return f"{image_url}?tag={image_tag}" if image_tag else image_url
|
||||
|
||||
def get_resume(self, num: Optional[int] = 12, username: Optional[str] = None) -> Optional[
|
||||
List[_SchemaMediaServerPlayItem]]:
|
||||
@@ -1279,7 +1284,7 @@ class Emby:
|
||||
params = {
|
||||
"Limit": 100,
|
||||
"MediaTypes": "Video",
|
||||
"Fields": "ProductionYear,Path",
|
||||
"Fields": "ProductionYear,Path,ImageTags",
|
||||
"api_key": self._apikey,
|
||||
}
|
||||
try:
|
||||
@@ -1312,12 +1317,18 @@ class Emby:
|
||||
image = self.get_backdrop_url(item_id=item.get("Id"),
|
||||
image_tag=item.get("BackdropImageTags")[0])
|
||||
else:
|
||||
image = self.__get_local_image_by_id(item.get("Id"))
|
||||
image = self.__get_local_image_by_id(
|
||||
item.get("Id"),
|
||||
image_tag=(item.get("ImageTags") or {}).get("Primary"),
|
||||
)
|
||||
else:
|
||||
image = self.get_backdrop_url(item_id=item.get("SeriesId"),
|
||||
image_tag=item.get("SeriesPrimaryImageTag"))
|
||||
if not image:
|
||||
image = self.__get_local_image_by_id(item.get("SeriesId"))
|
||||
image = self.__get_local_image_by_id(
|
||||
item.get("SeriesId"),
|
||||
image_tag=item.get("SeriesPrimaryImageTag"),
|
||||
)
|
||||
ret_resume.append(_SchemaMediaServerPlayItem(
|
||||
id=item.get("Id"),
|
||||
item_id=item.get("Id"),
|
||||
@@ -1354,7 +1365,7 @@ class Emby:
|
||||
params = {
|
||||
"Limit": 100,
|
||||
"MediaTypes": "Video",
|
||||
"Fields": "ProductionYear,Path,BackdropImageTags",
|
||||
"Fields": "ProductionYear,Path,BackdropImageTags,ImageTags",
|
||||
"api_key": self._apikey
|
||||
}
|
||||
try:
|
||||
@@ -1376,7 +1387,10 @@ class Emby:
|
||||
item_type = MediaType.MOVIE.value if item.get("Type") == "Movie" else MediaType.TV.value
|
||||
server_id = item.get("ServerId") or self.serverid
|
||||
link = self.get_play_url(item.get("Id"), server_id=server_id)
|
||||
image = self.__get_local_image_by_id(item_id=item.get("Id"))
|
||||
image = self.__get_local_image_by_id(
|
||||
item_id=item.get("Id"),
|
||||
image_tag=(item.get("ImageTags") or {}).get("Primary"),
|
||||
)
|
||||
ret_latest.append(_SchemaMediaServerPlayItem(
|
||||
id=item.get("Id"),
|
||||
item_id=item.get("Id"),
|
||||
|
||||
106
tests/test_emby_image_urls.py
Normal file
106
tests/test_emby_image_urls.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""验证 Emby 图片版本标签参与图片 URL 构造。"""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from app.modules.emby.emby import Emby
|
||||
|
||||
|
||||
class _FakeResponse:
|
||||
"""提供 Emby 接口响应的最小 JSON 封装。"""
|
||||
|
||||
def __init__(self, payload):
|
||||
"""保存测试响应体。"""
|
||||
self._payload = payload
|
||||
|
||||
def json(self):
|
||||
"""返回测试响应体。"""
|
||||
return self._payload
|
||||
|
||||
|
||||
def _client() -> Emby:
|
||||
"""构造跳过远端初始化的 Emby 客户端。"""
|
||||
client = Emby.__new__(Emby)
|
||||
client._host = "http://emby.local/"
|
||||
client._playhost = None
|
||||
client._apikey = "api-key"
|
||||
client._sync_libraries = []
|
||||
client.user = "user-id"
|
||||
client.serverid = "server-id"
|
||||
return client
|
||||
|
||||
|
||||
def test_local_primary_image_url_includes_image_tag() -> None:
|
||||
"""封面标签应成为 URL 的版本参数,使缓存键随原地换图变化。"""
|
||||
client = _client()
|
||||
|
||||
assert client._Emby__get_local_image_by_id("item-id", "primary-tag") == (
|
||||
"http://emby.local/Items/item-id/Images/Primary?tag=primary-tag"
|
||||
)
|
||||
assert client._Emby__get_local_image_by_id("item-id") == (
|
||||
"http://emby.local/Items/item-id/Images/Primary"
|
||||
)
|
||||
|
||||
|
||||
def test_get_librarys_uses_primary_image_tag() -> None:
|
||||
"""媒体库封面应使用 Views 返回的 Primary 标签。"""
|
||||
client = _client()
|
||||
client.get_items_count = Mock(return_value=0)
|
||||
|
||||
with patch.object(client, "_Emby__get_emby_librarys") as librarys:
|
||||
librarys.return_value = [{
|
||||
"Id": "library-id",
|
||||
"Name": "电影库",
|
||||
"CollectionType": "movies",
|
||||
"ImageTags": {"Primary": "library-tag"},
|
||||
}]
|
||||
|
||||
result = client.get_librarys()
|
||||
|
||||
assert result[0].image == (
|
||||
"http://emby.local/Items/library-id/Images/Primary?tag=library-tag"
|
||||
)
|
||||
|
||||
|
||||
def test_get_resume_uses_primary_image_tag_for_movie_fallback() -> None:
|
||||
"""继续观看电影没有背景图时应使用电影主图标签。"""
|
||||
client = _client()
|
||||
client.get_user_library_folders = Mock(return_value=[])
|
||||
|
||||
with patch("app.modules.emby.emby.RequestUtils") as request_utils_cls:
|
||||
request_utils_cls.return_value.get_res.return_value = _FakeResponse({
|
||||
"Items": [{
|
||||
"Id": "movie-id",
|
||||
"Type": "Movie",
|
||||
"Name": "测试电影",
|
||||
"ImageTags": {"Primary": "movie-tag"},
|
||||
"BackdropImageTags": [],
|
||||
}]
|
||||
})
|
||||
|
||||
result = client.get_resume()
|
||||
|
||||
assert result[0].image == "http://emby.local/Items/movie-id/Images/Primary?tag=movie-tag"
|
||||
assert request_utils_cls.return_value.get_res.call_args.args[1]["Fields"] == (
|
||||
"ProductionYear,Path,ImageTags"
|
||||
)
|
||||
|
||||
|
||||
def test_get_latest_uses_primary_image_tag() -> None:
|
||||
"""最近入库条目的主图 URL 应携带图片标签。"""
|
||||
client = _client()
|
||||
client.get_user_library_folders = Mock(return_value=[])
|
||||
|
||||
with patch("app.modules.emby.emby.RequestUtils") as request_utils_cls:
|
||||
request_utils_cls.return_value.get_res.return_value = _FakeResponse([{
|
||||
"Id": "movie-id",
|
||||
"Type": "Movie",
|
||||
"Name": "测试电影",
|
||||
"ImageTags": {"Primary": "movie-tag"},
|
||||
}])
|
||||
|
||||
result = client.get_latest()
|
||||
|
||||
assert result[0].image == "http://emby.local/Items/movie-id/Images/Primary?tag=movie-tag"
|
||||
assert request_utils_cls.return_value.get_res.call_args.args[1]["Fields"] == (
|
||||
"ProductionYear,Path,BackdropImageTags,ImageTags"
|
||||
)
|
||||
Reference in New Issue
Block a user