mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-15 19:14:01 +08:00
543 lines
16 KiB
Python
543 lines
16 KiB
Python
from typing import Annotated, Optional, Dict, List, Union, Any
|
||
|
||
from pydantic import BaseModel, Discriminator, Field, RootModel, Tag
|
||
|
||
from app.schemas.common import JsonData
|
||
from app.schemas.music import MusicInfo, MusicMeta
|
||
from app.schemas.media import OptionalMediaIdentityMixin
|
||
from app.schemas.types import MediaSource
|
||
|
||
|
||
class MetaInfo(OptionalMediaIdentityMixin, BaseModel):
|
||
"""
|
||
识别元数据
|
||
"""
|
||
# 是否处理的文件
|
||
isfile: Optional[bool] = False
|
||
# 原字符串
|
||
org_string: Optional[str] = None
|
||
# 原标题
|
||
title: Optional[str] = None
|
||
# 副标题
|
||
subtitle: Optional[str] = None
|
||
# 类型 电影、电视剧、音乐
|
||
type: Optional[str] = None
|
||
# 名称
|
||
name: Optional[str] = None
|
||
# 识别的中文名
|
||
cn_name: Optional[str] = None
|
||
# 识别的英文名
|
||
en_name: Optional[str] = None
|
||
# 年份
|
||
year: Optional[str] = None
|
||
# 总季数
|
||
total_season: Optional[int] = 0
|
||
# 识别的开始季 数字
|
||
begin_season: Optional[int] = None
|
||
# 识别的结束季 数字
|
||
end_season: Optional[int] = None
|
||
# 总集数
|
||
total_episode: Optional[int] = 0
|
||
# 识别的开始集
|
||
begin_episode: Optional[int] = None
|
||
# 识别的结束集
|
||
end_episode: Optional[int] = None
|
||
# SxxExx
|
||
season_episode: Optional[str] = None
|
||
# 集列表
|
||
episode_list: Optional[List[int]] = Field(default_factory=list)
|
||
# Partx Cd Dvd Disk Disc
|
||
part: Optional[str] = None
|
||
# 识别的资源类型
|
||
resource_type: Optional[str] = None
|
||
# 识别的效果
|
||
resource_effect: Optional[str] = None
|
||
# 识别的分辨率
|
||
resource_pix: Optional[str] = None
|
||
# 识别的制作组/字幕组
|
||
resource_team: Optional[str] = None
|
||
# 视频编码
|
||
video_encode: Optional[str] = None
|
||
# 音频编码
|
||
audio_encode: Optional[str] = None
|
||
# 资源类型
|
||
edition: Optional[str] = None
|
||
# 流媒体平台
|
||
web_source: Optional[str] = None
|
||
# 应用的识别词信息
|
||
apply_words: Optional[List[str]] = None
|
||
# 剧集组
|
||
episode_group: Optional[str] = None
|
||
# 显式媒体数据源
|
||
media_source: Optional[MediaSource] = None
|
||
# 显式媒体数据源原生ID
|
||
media_id: Optional[str] = None
|
||
|
||
|
||
class MediaImageSet(BaseModel):
|
||
"""跨媒体源兼容的人物图片尺寸集合。"""
|
||
|
||
large: Optional[str] = None
|
||
common: Optional[str] = None
|
||
medium: Optional[str] = None
|
||
normal: Optional[str] = None
|
||
small: Optional[str] = None
|
||
grid: Optional[str] = None
|
||
|
||
|
||
class MediaCredit(BaseModel):
|
||
"""影视条目中的演职员摘要。"""
|
||
|
||
id: Optional[int | str] = None
|
||
name: Optional[str] = None
|
||
original_name: Optional[str] = None
|
||
character: Optional[str] = None
|
||
type: Optional[str | int] = None
|
||
gender: Optional[str | int] = None
|
||
adult: Optional[bool] = None
|
||
known_for_department: Optional[str] = None
|
||
profile_path: Optional[str] = None
|
||
credit_id: Optional[str] = None
|
||
cast_id: Optional[int] = None
|
||
order: Optional[int] = None
|
||
department: Optional[str] = None
|
||
job: Optional[str] = None
|
||
popularity: Optional[float] = None
|
||
roles: list[str] = Field(default_factory=list)
|
||
title: Optional[str] = None
|
||
url: Optional[str] = None
|
||
uri: Optional[str] = None
|
||
sharing_url: Optional[str] = None
|
||
avatar: Optional[str | MediaImageSet] = None
|
||
images: Optional[MediaImageSet] = None
|
||
latin_name: Optional[str] = None
|
||
career: list[str] = Field(default_factory=list)
|
||
relation: Optional[str] = None
|
||
user: Optional[JsonData] = None
|
||
|
||
|
||
class MediaGenre(BaseModel):
|
||
"""影视风格摘要。"""
|
||
|
||
id: Optional[int | str] = None
|
||
name: Optional[str] = None
|
||
|
||
|
||
class MediaCompany(BaseModel):
|
||
"""电视网或制作公司的标准摘要。"""
|
||
|
||
id: Optional[int | str] = None
|
||
name: Optional[str] = None
|
||
logo_path: Optional[str] = None
|
||
origin_country: Optional[str] = None
|
||
|
||
|
||
class MediaCountry(BaseModel):
|
||
"""影视制作国家或地区。"""
|
||
|
||
id: Optional[int | str] = None
|
||
iso_3166_1: Optional[str] = None
|
||
name: Optional[str] = None
|
||
|
||
|
||
class MediaLanguage(BaseModel):
|
||
"""影视内容使用的语言。"""
|
||
|
||
english_name: Optional[str] = None
|
||
iso_639_1: Optional[str] = None
|
||
name: Optional[str] = None
|
||
|
||
|
||
class MediaReleaseDate(BaseModel):
|
||
"""电影在单个地区的一次发行记录。"""
|
||
|
||
date: str
|
||
iso_code: Optional[str] = None
|
||
note: Optional[str] = None
|
||
type: Optional[int] = None
|
||
|
||
|
||
class MediaEpisode(BaseModel):
|
||
"""电视剧即将播出的单集摘要。"""
|
||
|
||
id: Optional[int] = None
|
||
air_date: Optional[str] = None
|
||
episode_number: Optional[int] = None
|
||
episode_type: Optional[str] = None
|
||
name: Optional[str] = None
|
||
overview: Optional[str] = None
|
||
production_code: Optional[str] = None
|
||
runtime: Optional[int] = None
|
||
season_number: Optional[int] = None
|
||
show_id: Optional[int] = None
|
||
still_path: Optional[str] = None
|
||
vote_average: Optional[float] = None
|
||
vote_count: Optional[int] = None
|
||
|
||
|
||
class MediaSeason(BaseModel):
|
||
"""标准季信息以及剧集组季信息。"""
|
||
|
||
id: Optional[int | str] = None
|
||
air_date: Optional[str] = None
|
||
episode_count: Optional[int] = None
|
||
name: Optional[str] = None
|
||
overview: Optional[str] = None
|
||
poster_path: Optional[str] = None
|
||
season_number: Optional[int] = None
|
||
vote_average: Optional[float] = None
|
||
order: Optional[int] = None
|
||
locked: Optional[bool] = None
|
||
episodes: list[MediaEpisode] = Field(default_factory=list)
|
||
|
||
|
||
class MediaEpisodeGroupNetwork(BaseModel):
|
||
"""TMDB 剧集组所属电视网信息。"""
|
||
|
||
id: Optional[int] = None
|
||
name: Optional[str] = None
|
||
logo_path: Optional[str] = None
|
||
origin_country: Optional[str] = None
|
||
|
||
|
||
class MediaEpisodeGroup(BaseModel):
|
||
"""TMDB 电视剧的剧集分组摘要。"""
|
||
|
||
description: str = ""
|
||
episode_count: int = 0
|
||
group_count: int = 0
|
||
id: str
|
||
name: str
|
||
network: Optional[MediaEpisodeGroupNetwork] = None
|
||
type: int
|
||
|
||
|
||
class MediaInfo(OptionalMediaIdentityMixin, BaseModel):
|
||
"""
|
||
识别媒体信息
|
||
"""
|
||
# 媒体主身份来源
|
||
media_source: Optional[MediaSource] = None
|
||
# 请求级刮削来源
|
||
scrape_source: Optional[str] = None
|
||
# 类型 电影、电视剧、合集
|
||
type: Optional[str] = None
|
||
# 媒体标题
|
||
title: Optional[str] = None
|
||
# 英文标题
|
||
en_title: Optional[str] = None
|
||
# 香港、台湾、新加坡地区标题
|
||
hk_title: Optional[str] = None
|
||
tw_title: Optional[str] = None
|
||
sg_title: Optional[str] = None
|
||
# 年份
|
||
year: Optional[str] = None
|
||
# 标题(年份)
|
||
title_year: Optional[str] = None
|
||
# 当前指定季,如有
|
||
season: Optional[int] = None
|
||
# 数据源返回的辅助 ID,仅作为元数据输出
|
||
tmdb_id: Optional[int] = None
|
||
imdb_id: Optional[str] = None
|
||
tvdb_id: Optional[int] = None
|
||
tvdb_slug: Optional[str] = None
|
||
douban_id: Optional[str] = None
|
||
bangumi_id: Optional[int] = None
|
||
anilist_id: Optional[int] = None
|
||
anidb_id: Optional[int] = None
|
||
# 合集ID
|
||
collection_id: Optional[int] = None
|
||
# 当前来源原生 ID
|
||
media_id: Optional[str] = None
|
||
# 媒体原语种
|
||
original_language: Optional[str] = None
|
||
# 媒体原发行标题
|
||
original_title: Optional[str] = None
|
||
# 媒体发行日期
|
||
release_date: Optional[str] = None
|
||
# 背景图片
|
||
backdrop_path: Optional[str] = None
|
||
# 海报图片
|
||
poster_path: Optional[str] = None
|
||
# 标题 LOGO
|
||
logo_path: Optional[str] = None
|
||
# 评分
|
||
vote_average: Optional[float] = 0.0
|
||
# 描述
|
||
overview: Optional[str] = None
|
||
# 二级分类
|
||
category: Optional[str] = ""
|
||
# 季季集清单
|
||
seasons: Optional[Dict[int, list[int]]] = Field(default_factory=dict)
|
||
# 季详情
|
||
season_info: Optional[List[MediaSeason]] = Field(default_factory=list)
|
||
# 各季首播年份
|
||
season_years: Optional[Dict[int, str]] = Field(default_factory=dict)
|
||
# 别名和译名
|
||
names: Optional[list[str]] = Field(default_factory=list)
|
||
# 演员
|
||
actors: Optional[list[Union[MediaCredit, str]]] = Field(default_factory=list)
|
||
# 导演
|
||
directors: Optional[list[Union[MediaCredit, str]]] = Field(default_factory=list)
|
||
# 详情链接
|
||
detail_link: Optional[str] = None
|
||
# 其它TMDB属性
|
||
# 是否成人内容
|
||
adult: Optional[bool] = False
|
||
# 创建人
|
||
created_by: Optional[list[MediaCredit]] = Field(default_factory=list)
|
||
# 集时长
|
||
episode_run_time: Optional[list[int]] = Field(default_factory=list)
|
||
# 风格
|
||
genres: Optional[List[MediaGenre]] = Field(default_factory=list)
|
||
# 首播日期
|
||
first_air_date: Optional[str] = None
|
||
# 首页
|
||
homepage: Optional[str] = None
|
||
# 语种
|
||
languages: Optional[list[str]] = Field(default_factory=list)
|
||
# 最后上映日期
|
||
last_air_date: Optional[str] = None
|
||
# 流媒体平台
|
||
networks: Optional[list[MediaCompany]] = Field(default_factory=list)
|
||
# 集数
|
||
number_of_episodes: Optional[int] = 0
|
||
# 季数
|
||
number_of_seasons: Optional[int] = 0
|
||
# 原产国
|
||
origin_country: Optional[list[str]] = Field(default_factory=list)
|
||
# 原名
|
||
original_name: Optional[str] = None
|
||
# 出品公司
|
||
production_companies: Optional[list[MediaCompany]] = Field(default_factory=list)
|
||
# 出品国
|
||
production_countries: Optional[list[MediaCountry]] = Field(default_factory=list)
|
||
# 语种
|
||
spoken_languages: Optional[list[MediaLanguage]] = Field(default_factory=list)
|
||
# 所有发行日期
|
||
release_dates: list[MediaReleaseDate] = Field(default_factory=list)
|
||
# 状态
|
||
status: Optional[str] = None
|
||
# 标签
|
||
tagline: Optional[str] = None
|
||
# 风格ID
|
||
genre_ids: Optional[list[int | str]] = Field(default_factory=list)
|
||
# 评价数量
|
||
vote_count: Optional[int] = 0
|
||
# 流行度
|
||
popularity: Optional[float] = 0.0
|
||
# 时长
|
||
runtime: Optional[int] = None
|
||
# 下一集
|
||
next_episode_to_air: Optional[MediaEpisode] = None
|
||
# 内容分级
|
||
content_rating: Optional[str] = None
|
||
# 全部剧集组
|
||
episode_groups: Optional[list[MediaEpisodeGroup | MediaSeason]] = Field(default_factory=list)
|
||
# 剧集组
|
||
episode_group: Optional[str] = None
|
||
# 各数据源原始信息;Core MediaInfo.to_dict() 保留这些键供兼容调用方使用。
|
||
tmdb_info: Optional[dict[str, JsonData]] = None
|
||
douban_info: Optional[dict[str, JsonData]] = None
|
||
bangumi_info: Optional[dict[str, JsonData]] = None
|
||
anilist_info: Optional[dict[str, JsonData]] = None
|
||
|
||
|
||
class TorrentInfo(OptionalMediaIdentityMixin, BaseModel):
|
||
"""
|
||
搜索种子信息
|
||
"""
|
||
# 站点ID
|
||
site: Optional[int] = None
|
||
# 站点名称
|
||
site_name: Optional[str] = None
|
||
# 站点Cookie
|
||
site_cookie: Optional[str] = None
|
||
# 站点UA
|
||
site_ua: Optional[str] = None
|
||
# 站点是否使用代理
|
||
site_proxy: Optional[bool] = False
|
||
# 站点优先级
|
||
site_order: Optional[int] = 0
|
||
# 站点下载器
|
||
site_downloader: Optional[str] = None
|
||
# 种子名称
|
||
title: Optional[str] = None
|
||
# 种子副标题
|
||
description: Optional[str] = None
|
||
# 种子页面声明的媒体身份
|
||
media_source: Optional[MediaSource] = None
|
||
media_id: Optional[str] = None
|
||
# 种子链接
|
||
enclosure: Optional[str] = None
|
||
# 详情页面
|
||
page_url: Optional[str] = None
|
||
# 种子大小
|
||
size: Optional[float] = 0.0
|
||
# 做种者
|
||
seeders: Optional[int] = 0
|
||
# 下载者
|
||
peers: Optional[int] = 0
|
||
# 完成者
|
||
grabs: Optional[int] = 0
|
||
# 发布时间
|
||
pubdate: Optional[str] = None
|
||
# 已过时间
|
||
date_elapsed: Optional[str] = None
|
||
# 免费截止时间
|
||
freedate: Optional[str] = None
|
||
# 上传因子
|
||
uploadvolumefactor: Optional[float] = None
|
||
# 下载因子
|
||
downloadvolumefactor: Optional[float] = None
|
||
# HR
|
||
hit_and_run: Optional[bool] = False
|
||
# 种子标签
|
||
labels: Optional[list[str]] = Field(default_factory=list)
|
||
# 种子优先级
|
||
pri_order: Optional[int] = 0
|
||
# 种子分类 电影/电视剧/音乐
|
||
category: Optional[str] = None
|
||
# 促销
|
||
volume_factor: Optional[str] = None
|
||
# 剩余免费时间
|
||
freedate_diff: Optional[str] = None
|
||
|
||
|
||
class SubtitleInfo(BaseModel):
|
||
"""
|
||
搜索字幕信息
|
||
"""
|
||
# 站点ID
|
||
site: Optional[int] = None
|
||
# 站点名称
|
||
site_name: Optional[str] = None
|
||
# 站点Cookie
|
||
site_cookie: Optional[str] = None
|
||
# 站点UA
|
||
site_ua: Optional[str] = None
|
||
# 站点是否使用代理
|
||
site_proxy: Optional[bool] = False
|
||
# 站点优先级
|
||
site_order: Optional[int] = 0
|
||
# 字幕标题
|
||
title: Optional[str] = None
|
||
# 字幕描述
|
||
description: Optional[str] = None
|
||
# 字幕下载链接
|
||
enclosure: Optional[str] = None
|
||
# 详情页面
|
||
page_url: Optional[str] = None
|
||
# 语言
|
||
language: Optional[str] = None
|
||
# 语言图标
|
||
language_icon: Optional[str] = None
|
||
# 字幕大小
|
||
size: Optional[float] = 0.0
|
||
# 发布时间
|
||
pubdate: Optional[str] = None
|
||
# 已过时间
|
||
date_elapsed: Optional[str] = None
|
||
# 点击/下载次数
|
||
grabs: Optional[int] = 0
|
||
# 上传者
|
||
uploader: Optional[str] = None
|
||
# 举报页面
|
||
report_url: Optional[str] = None
|
||
# 种子ID
|
||
torrent_id: Optional[str] = None
|
||
# 字幕ID
|
||
subtitle_id: Optional[str] = None
|
||
# 下载文件名
|
||
file_name: Optional[str] = None
|
||
# 识别元数据
|
||
meta_info: Optional[MetaInfo] = None
|
||
# SxxExx
|
||
season_episode: Optional[str] = None
|
||
# 集列表
|
||
episode_list: Optional[List[int]] = Field(default_factory=list)
|
||
|
||
|
||
class Context(BaseModel):
|
||
"""
|
||
上下文
|
||
"""
|
||
# 元数据
|
||
meta_info: Optional[Union[MusicMeta, MetaInfo]] = None
|
||
# 媒体信息
|
||
media_info: Optional[Union[MusicInfo, MediaInfo]] = None
|
||
# 种子信息
|
||
torrent_info: Optional[TorrentInfo] = None
|
||
# 候选资源来源:rss、spider、search、unknown
|
||
resource_source: Optional[str] = "unknown"
|
||
# 候选匹配来源:MediaSource 枚举值、title、unknown
|
||
match_source: Optional[str] = "unknown"
|
||
# 候选自身是否已经识别出有效媒体 ID
|
||
candidate_recognized: Optional[bool] = False
|
||
# 当前 media_info 是否为目标媒体回填
|
||
media_info_is_target: Optional[bool] = False
|
||
# 下载层确认候选资源覆盖完整目标范围,供订阅事实写入判断整包资源
|
||
confirmed_full_coverage: Optional[bool] = False
|
||
|
||
|
||
class MediaPerson(BaseModel):
|
||
"""
|
||
媒体人物信息
|
||
"""
|
||
# 来源:themoviedb、douban、bangumi、anilist
|
||
source: Optional[str] = None
|
||
# 公共
|
||
id: Optional[int | str] = None
|
||
type: Optional[Union[str, int]] = 1
|
||
name: Optional[str] = None
|
||
character: Optional[str] = None
|
||
images: Optional[MediaImageSet] = None
|
||
# themoviedb
|
||
profile_path: Optional[str] = None
|
||
gender: Optional[Union[str, int]] = None
|
||
original_name: Optional[str] = None
|
||
credit_id: Optional[str] = None
|
||
also_known_as: Optional[list[str]] = Field(default_factory=list)
|
||
birthday: Optional[str] = None
|
||
deathday: Optional[str] = None
|
||
imdb_id: Optional[str] = None
|
||
known_for_department: Optional[str] = None
|
||
place_of_birth: Optional[str] = None
|
||
popularity: Optional[float] = None
|
||
biography: Optional[str] = None
|
||
# douban
|
||
roles: Optional[list[str]] = Field(default_factory=list)
|
||
title: Optional[str] = None
|
||
url: Optional[str] = None
|
||
avatar: Optional[Union[str, MediaImageSet]] = None
|
||
latin_name: Optional[str] = None
|
||
# bangumi
|
||
career: Optional[list[str]] = Field(default_factory=list)
|
||
relation: Optional[str] = None
|
||
|
||
|
||
def _media_search_result_kind(value: Any) -> str:
|
||
"""按稳定字段区分音乐、人物与影视/合集搜索结果。"""
|
||
if isinstance(value, BaseModel):
|
||
value = value.model_dump()
|
||
if isinstance(value, dict):
|
||
if value.get("type") == "音乐" or "music_type" in value:
|
||
return "music"
|
||
if "source" in value and "media_source" not in value:
|
||
return "person"
|
||
return "media"
|
||
|
||
|
||
MediaSearchResult = Annotated[
|
||
Union[
|
||
Annotated[MusicInfo, Tag("music")],
|
||
Annotated[MediaPerson, Tag("person")],
|
||
Annotated[MediaInfo, Tag("media")],
|
||
],
|
||
Discriminator(_media_search_result_kind),
|
||
]
|
||
|
||
|
||
class MediaSearchResults(RootModel[List[MediaSearchResult]]):
|
||
"""媒体、音乐、合集与人物的统一搜索结果列表。"""
|