Merge pull request #2755 from qcgzxw/mediaserver

This commit is contained in:
jxxghp
2024-09-20 19:04:54 +08:00
committed by GitHub
8 changed files with 201 additions and 112 deletions
+8 -2
View File
@@ -27,11 +27,17 @@ class MediaServerChain(ChainBase):
""" """
return self.run_module("mediaserver_librarys", server=server, username=username, hidden=hidden) return self.run_module("mediaserver_librarys", server=server, username=username, hidden=hidden)
def items(self, server: str, library_id: Union[str, int]) -> List[schemas.MediaServerItem]: def items(self, server: str, library_id: Union[str, int], start_index: int = 0, limit: int = 100) -> List[schemas.MediaServerItem]:
""" """
获取媒体服务器所有项目 获取媒体服务器所有项目
""" """
return self.run_module("mediaserver_items", server=server, library_id=library_id) data = []
data_generator = self.run_module("mediaserver_items", server=server, library_id=library_id, start_index=start_index, limit=limit)
if data_generator:
for item in data_generator:
if item:
data.append(item)
return data
def iteminfo(self, server: str, item_id: Union[str, int]) -> schemas.MediaServerItem: def iteminfo(self, server: str, item_id: Union[str, int]) -> schemas.MediaServerItem:
""" """
+2 -2
View File
@@ -182,13 +182,13 @@ class EmbyModule(_ModuleBase, _MediaServerBase):
return server.get_librarys(username=username, hidden=hidden) return server.get_librarys(username=username, hidden=hidden)
return None return None
def mediaserver_items(self, server: str, library_id: str) -> Optional[Generator]: def mediaserver_items(self, server: str, library_id: str, start_index: int = 0, limit: int = 100) -> Optional[Generator]:
""" """
媒体库项目列表 媒体库项目列表
""" """
server: Emby = self.get_server(server) server: Emby = self.get_server(server)
if server: if server:
return server.get_items(library_id) return server.get_items(library_id, start_index, limit)
return None return None
def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]: def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]:
+74 -50
View File
@@ -1,6 +1,7 @@
import json import json
import re import re
import traceback import traceback
from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import List, Optional, Union, Dict, Generator, Tuple from typing import List, Optional, Union, Dict, Generator, Tuple
@@ -352,7 +353,7 @@ class Emby:
url = f"{self._host}emby/Items" url = f"{self._host}emby/Items"
params = { params = {
"IncludeItemTypes": "Movie", "IncludeItemTypes": "Movie",
"Fields": "ProductionYear", "Fields": "ProviderIds,OriginalTitle,ProductionYear,Path,UserDataPlayCount,UserDataLastPlayedDate,ParentId",
"StartIndex": 0, "StartIndex": 0,
"Recursive": "true", "Recursive": "true",
"SearchTerm": title, "SearchTerm": title,
@@ -366,29 +367,14 @@ class Emby:
res_items = res.json().get("Items") res_items = res.json().get("Items")
if res_items: if res_items:
ret_movies = [] ret_movies = []
for res_item in res_items: for item in res_items:
item_tmdbid = res_item.get("ProviderIds", {}).get("Tmdb") if not item:
mediaserver_item = schemas.MediaServerItem(
server="emby",
library=res_item.get("ParentId"),
item_id=res_item.get("Id"),
item_type=res_item.get("Type"),
title=res_item.get("Name"),
original_title=res_item.get("OriginalTitle"),
year=res_item.get("ProductionYear"),
tmdbid=int(item_tmdbid) if item_tmdbid else None,
imdbid=res_item.get("ProviderIds", {}).get("Imdb"),
tvdbid=res_item.get("ProviderIds", {}).get("Tvdb"),
path=res_item.get("Path")
)
if tmdb_id and item_tmdbid:
if str(item_tmdbid) != str(tmdb_id):
continue continue
else: mediaserver_item = self.__format_item_info(item)
ret_movies.append(mediaserver_item) if mediaserver_item:
continue if (not tmdb_id or mediaserver_item.tmdbid == tmdb_id) and \
if (mediaserver_item.title == title mediaserver_item.title == title and \
and (not year or str(mediaserver_item.year) == str(year))): (not year or str(mediaserver_item.year) == str(year)):
ret_movies.append(mediaserver_item) ret_movies.append(mediaserver_item)
return ret_movies return ret_movies
except Exception as e: except Exception as e:
@@ -615,6 +601,48 @@ class Emby:
# 刷新根目录 # 刷新根目录
return "/" return "/"
def __format_item_info(self, item) -> Optional[schemas.MediaServerItem]:
"""
格式化item
"""
try:
user_data = item.get("UserData", {})
if not user_data:
user_state = None
else:
resume = item.get("UserData", {}).get("PlaybackPositionTicks") and item.get("UserData", {}).get("PlaybackPositionTicks") > 0
last_played_date = item.get("UserData", {}).get("LastPlayedDate")
if last_played_date is not None and "." in last_played_date:
last_played_date = last_played_date.split(".")[0]
user_state = schemas.MediaServerItemUserState(
played=item.get("UserData", {}).get("Played"),
resume=resume,
last_played_date=datetime.strptime(last_played_date, "%Y-%m-%dT%H:%M:%S").strftime(
"%Y-%m-%d %H:%M:%S") if last_played_date else None,
play_count=item.get("UserData", {}).get("PlayCount"),
percentage=item.get("UserData", {}).get("PlayedPercentage"),
)
tmdbid = item.get("ProviderIds", {}).get("Tmdb")
return schemas.MediaServerItem(
id=item.get("Id"),
server="emby",
library=item.get("ParentId"),
item_id=item.get("Id"),
item_type=item.get("Type"),
title=item.get("Name"),
original_title=item.get("OriginalTitle"),
year=item.get("ProductionYear"),
tmdbid=int(tmdbid) if tmdbid else None,
imdbid=item.get("ProviderIds", {}).get("Imdb"),
tvdbid=item.get("ProviderIds", {}).get("Tvdb"),
path=item.get("Path"),
user_state=user_state
)
except Exception as e:
logger.error(e)
return None
def get_iteminfo(self, itemid: str) -> Optional[schemas.MediaServerItem]: def get_iteminfo(self, itemid: str) -> Optional[schemas.MediaServerItem]:
""" """
获取单个项目详情 获取单个项目详情
@@ -630,28 +658,19 @@ class Emby:
try: try:
res = RequestUtils().get_res(url, params) res = RequestUtils().get_res(url, params)
if res and res.status_code == 200: if res and res.status_code == 200:
item = res.json() iteminfo = self.__format_item_info(res.json())
tmdbid = item.get("ProviderIds", {}).get("Tmdb") return iteminfo
return schemas.MediaServerItem(
server="emby",
library=item.get("ParentId"),
item_id=item.get("Id"),
item_type=item.get("Type"),
title=item.get("Name"),
original_title=item.get("OriginalTitle"),
year=item.get("ProductionYear"),
tmdbid=int(tmdbid) if tmdbid else None,
imdbid=item.get("ProviderIds", {}).get("Imdb"),
tvdbid=item.get("ProviderIds", {}).get("Tvdb"),
path=item.get("Path")
)
except Exception as e: except Exception as e:
logger.error(f"连接Items/Id出错:" + str(e)) logger.error(f"连接/Users/{self.user}/Items/{itemid}出错:" + str(e))
return None return None
def get_items(self, parent: str) -> Generator: def get_items(self, parent: str, start_index: int = 0, limit: int = 100) -> Generator:
""" """
获取媒体服务器所有媒体库列表 获取媒体服务器所有媒体库列表
:param parent: 父媒体库ID
:param start_index: 开始索引,用于分页
:param limit: 每次请求返回的项目数量
:return: 生成器 schemas.MediaServerItem
""" """
if not parent: if not parent:
yield None yield None
@@ -660,20 +679,25 @@ class Emby:
url = f"{self._host}emby/Users/{self.user}/Items" url = f"{self._host}emby/Users/{self.user}/Items"
params = { params = {
"ParentId": parent, "ParentId": parent,
"api_key": self._apikey "api_key": self._apikey,
"Fields": "ProviderIds,OriginalTitle,ProductionYear,Path,UserDataPlayCount,UserDataLastPlayedDate,ParentId",
"StartIndex": start_index,
"Limit": limit
} }
try: try:
res = RequestUtils().get_res(url, params) res = RequestUtils().get_res(url, params)
if res and res.status_code == 200: if not res or res.status_code != 200:
results = res.json().get("Items") or [] yield None
for result in results: items = res.json().get("Items") or []
if not result: for item in items:
if not item:
continue continue
if result.get("Type") in ["Movie", "Series"]: if "Folder" in item.get("Type"):
yield self.get_iteminfo(result.get("Id")) for items in self.get_items(parent=item.get('Id')):
elif "Folder" in result.get("Type"): yield items
for item in self.get_items(parent=result.get('Id')): elif item.get("Type") in ["Movie", "Series"]:
yield item yield self.__format_item_info(item)
except Exception as e: except Exception as e:
logger.error(f"连接Users/Items出错:" + str(e)) logger.error(f"连接Users/Items出错:" + str(e))
yield None yield None
+2 -2
View File
@@ -180,13 +180,13 @@ class JellyfinModule(_ModuleBase, _MediaServerBase):
return server.get_librarys(username=username, hidden=hidden) return server.get_librarys(username=username, hidden=hidden)
return None return None
def mediaserver_items(self, server: str, library_id: str) -> Optional[Generator]: def mediaserver_items(self, server: str, library_id: str, start_index: int = 0, limit: int = 100) -> Optional[Generator]:
""" """
媒体库项目列表 媒体库项目列表
""" """
server: Jellyfin = self.get_server(server) server: Jellyfin = self.get_server(server)
if server: if server:
return server.get_items(library_id) return server.get_items(library_id, start_index, limit)
return None return None
def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]: def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]:
+73 -48
View File
@@ -1,4 +1,5 @@
import json import json
from datetime import datetime
from typing import List, Union, Optional, Dict, Generator, Tuple from typing import List, Union, Optional, Dict, Generator, Tuple
from requests import Response from requests import Response
@@ -345,6 +346,8 @@ class Jellyfin:
url = f"{self._host}Users/{self.user}/Items" url = f"{self._host}Users/{self.user}/Items"
params = { params = {
"IncludeItemTypes": "Movie", "IncludeItemTypes": "Movie",
"Fields": "ProviderIds,OriginalTitle,ProductionYear,Path,UserDataPlayCount,UserDataLastPlayedDate,ParentId",
"StartIndex": 0,
"Recursive": "true", "Recursive": "true",
"searchTerm": title, "searchTerm": title,
"Limit": 10, "Limit": 10,
@@ -357,28 +360,13 @@ class Jellyfin:
if res_items: if res_items:
ret_movies = [] ret_movies = []
for item in res_items: for item in res_items:
item_tmdbid = item.get("ProviderIds", {}).get("Tmdb") if not item:
mediaserver_item = schemas.MediaServerItem(
server="jellyfin",
library=item.get("ParentId"),
item_id=item.get("Id"),
item_type=item.get("Type"),
title=item.get("Name"),
original_title=item.get("OriginalTitle"),
year=item.get("ProductionYear"),
tmdbid=int(item_tmdbid) if item_tmdbid else None,
imdbid=item.get("ProviderIds", {}).get("Imdb"),
tvdbid=item.get("ProviderIds", {}).get("Tvdb"),
path=item.get("Path")
)
if tmdb_id and item_tmdbid:
if str(item_tmdbid) != str(tmdb_id):
continue continue
else: mediaserver_item = self.__format_item_info(item)
ret_movies.append(mediaserver_item) if mediaserver_item:
continue if (not tmdb_id or mediaserver_item.tmdbid == tmdb_id) and \
if mediaserver_item.title == title and ( mediaserver_item.title == title and \
not year or str(mediaserver_item.year) == str(year)): (not year or str(mediaserver_item.year) == str(year)):
ret_movies.append(mediaserver_item) ret_movies.append(mediaserver_item)
return ret_movies return ret_movies
except Exception as e: except Exception as e:
@@ -674,6 +662,49 @@ class Jellyfin:
return eventItem return eventItem
def __format_item_info(self, item) -> Optional[schemas.MediaServerItem]:
"""
格式化item
"""
try:
user_data = item.get("UserData", {})
if not user_data:
user_state = None
else:
resume = item.get("UserData", {}).get("PlaybackPositionTicks") and item.get("UserData", {}).get("PlaybackPositionTicks") > 0
last_played_date = item.get("UserData", {}).get("LastPlayedDate")
if last_played_date is not None and "." in last_played_date:
last_played_date = last_played_date.split(".")[0]
user_state = schemas.MediaServerItemUserState(
played=item.get("UserData", {}).get("Played"),
resume=resume,
last_played_date=datetime.strptime(last_played_date, "%Y-%m-%dT%H:%M:%S").strftime(
"%Y-%m-%d %H:%M:%S") if last_played_date else None,
play_count=item.get("UserData", {}).get("PlayCount"),
percentage=item.get("UserData", {}).get("PlayedPercentage"),
)
tmdbid = item.get("ProviderIds", {}).get("Tmdb")
return schemas.MediaServerItem(
server="jellyfin",
id=item.get("Id"),
library=item.get("ParentId"),
item_id=item.get("Id"),
item_type=item.get("Type"),
title=item.get("Name"),
original_title=item.get("OriginalTitle"),
year=item.get("ProductionYear"),
tmdbid=int(tmdbid) if tmdbid else None,
imdbid=item.get("ProviderIds", {}).get("Imdb"),
tvdbid=item.get("ProviderIds", {}).get("Tvdb"),
path=item.get("Path"),
user_state=user_state
)
except Exception as e:
logger.error(e)
return None
def get_iteminfo(self, itemid: str) -> Optional[schemas.MediaServerItem]: def get_iteminfo(self, itemid: str) -> Optional[schemas.MediaServerItem]:
""" """
获取单个项目详情 获取单个项目详情
@@ -689,28 +720,18 @@ class Jellyfin:
try: try:
res = RequestUtils().get_res(url, params) res = RequestUtils().get_res(url, params)
if res and res.status_code == 200: if res and res.status_code == 200:
item = res.json() return self.__format_item_info(res.json())
tmdbid = item.get("ProviderIds", {}).get("Tmdb")
return schemas.MediaServerItem(
server="jellyfin",
library=item.get("ParentId"),
item_id=item.get("Id"),
item_type=item.get("Type"),
title=item.get("Name"),
original_title=item.get("OriginalTitle"),
year=item.get("ProductionYear"),
tmdbid=int(tmdbid) if tmdbid else None,
imdbid=item.get("ProviderIds", {}).get("Imdb"),
tvdbid=item.get("ProviderIds", {}).get("Tvdb"),
path=item.get("Path")
)
except Exception as e: except Exception as e:
logger.error(f"连接Users/Items出错" + str(e)) logger.error(f"连接Users/{self.user}/Items/{itemid}" + str(e))
return None return None
def get_items(self, parent: str) -> Generator: def get_items(self, parent: str, start_index: int = 0, limit: int = 100) -> Generator:
""" """
获取媒体服务器所有媒体库列表 获取媒体服务器所有媒体库列表
:param parent: 父媒体库ID
:param start_index: 开始索引,用于分页
:param limit: 每次请求返回的项目数量
:return: 生成器 schemas.MediaServerItem
""" """
if not parent: if not parent:
yield None yield None
@@ -719,20 +740,24 @@ class Jellyfin:
url = f"{self._host}Users/{self.user}/Items" url = f"{self._host}Users/{self.user}/Items"
params = { params = {
"parentId": parent, "parentId": parent,
"api_key": self._apikey "api_key": self._apikey,
"Fields": "ProviderIds,OriginalTitle,ProductionYear,Path,UserDataPlayCount,UserDataLastPlayedDate,ParentId",
"StartIndex": start_index,
"Limit": limit,
} }
try: try:
res = RequestUtils().get_res(url, params) res = RequestUtils().get_res(url, params)
if res and res.status_code == 200: if not res or res.status_code != 200:
results = res.json().get("Items") or [] yield None
for result in results: items = res.json().get("Items") or []
if not result: for item in items:
if not item:
continue continue
if result.get("Type") in ["Movie", "Series"]: if "Folder" in item.get("Type"):
yield self.get_iteminfo(result.get("Id")) for items in self.get_items(item.get("Id")):
elif "Folder" in result.get("Type"): yield items
for item in self.get_items(result.get("Id")): elif item.get("Type") in ["Movie", "Series"]:
yield item yield self.__format_item_info(item)
except Exception as e: except Exception as e:
logger.error(f"连接Users/Items出错:" + str(e)) logger.error(f"连接Users/Items出错:" + str(e))
yield None yield None
+2 -2
View File
@@ -168,13 +168,13 @@ class PlexModule(_ModuleBase, _MediaServerBase):
return server.get_librarys(hidden) return server.get_librarys(hidden)
return None return None
def mediaserver_items(self, server: str, library_id: str) -> Optional[Generator]: def mediaserver_items(self, server: str, library_id: str, start_index: int = 0, limit: int = 100) -> Optional[Generator]:
""" """
媒体库项目列表 媒体库项目列表
""" """
server: Plex = self.get_server(server) server: Plex = self.get_server(server)
if server: if server:
return server.get_items(library_id) return server.get_items(library_id, start_index, limit)
return None return None
def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]: def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]:
+22 -2
View File
@@ -446,9 +446,13 @@ class Plex:
return ids return ids
def get_items(self, parent: str) -> Generator: def get_items(self, parent: str, start_index: int = 0, limit: int = 100) -> Generator:
""" """
获取媒体服务器所有媒体库列表 获取媒体服务器所有媒体库列表
:param parent: 父媒体库ID
:param start_index: 开始索引,用于分页
:param limit: 每次请求返回的项目数量
:return: 生成器 schemas.MediaServerItem
""" """
if not parent: if not parent:
yield None yield None
@@ -457,7 +461,7 @@ class Plex:
try: try:
section = self._plex.library.sectionByID(int(parent)) section = self._plex.library.sectionByID(int(parent))
if section: if section:
for item in section.all(): for item in section.all(container_start=start_index, limit=limit):
try: try:
if not item: if not item:
continue continue
@@ -465,7 +469,22 @@ class Plex:
path = None path = None
if item.locations: if item.locations:
path = item.locations[0] path = item.locations[0]
playback_position = item.viewOffset if hasattr(item, 'viewOffset') else 0
duration = item.duration if hasattr(item, 'duration') else 0
percentage = (playback_position / duration * 100) if duration > 0 else None
played = item.isPlayed if hasattr(item, 'isPlayed') else False
play_count = item.viewCount if hasattr(item, 'viewCount') else 0
last_played_date = item.lastViewedAt if hasattr(item, 'lastViewedAt') else None
user_state = schemas.MediaServerItemUserState(
played=played,
resume=playback_position > 0,
last_played_date=last_played_date.isoformat() if last_played_date else None,
play_count=play_count,
percentage=percentage,
)
yield schemas.MediaServerItem( yield schemas.MediaServerItem(
id=item.ratingKey,
server="plex", server="plex",
library=item.librarySectionID, library=item.librarySectionID,
item_id=item.key, item_id=item.key,
@@ -477,6 +496,7 @@ class Plex:
imdbid=ids['imdb_id'], imdbid=ids['imdb_id'],
tvdbid=ids['tvdb_id'], tvdbid=ids['tvdb_id'],
path=path, path=path,
user_state=user_state,
) )
except Exception as e: except Exception as e:
logger.error(f"处理媒体项目时出错:{str(e)}, 跳过此项目。") logger.error(f"处理媒体项目时出错:{str(e)}, 跳过此项目。")
+14
View File
@@ -72,6 +72,19 @@ class MediaServerLibrary(BaseModel):
link: Optional[str] = None link: Optional[str] = None
class MediaServerItemUserState(BaseModel):
# 已播放
played: Optional[bool] = None
# 继续播放
resume: Optional[bool] = None
# 上次播放时间 10位时间戳
last_played_date: Optional[str] = None
# 播放次数(不等于完播次数,理解为浏览次数)
play_count: Optional[int] = None
# 播放进度
percentage: Optional[float] = None
class MediaServerItem(BaseModel): class MediaServerItem(BaseModel):
""" """
媒体服务器媒体信息 媒体服务器媒体信息
@@ -106,6 +119,7 @@ class MediaServerItem(BaseModel):
note: Optional[str] = None note: Optional[str] = None
# 同步时间 # 同步时间
lst_mod_date: Optional[str] = None lst_mod_date: Optional[str] = None
user_state: Optional[MediaServerItemUserState] = None
class Config: class Config:
orm_mode = True orm_mode = True