mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 03:56:43 +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]]):
|
|
"""媒体、音乐、合集与人物的统一搜索结果列表。"""
|