Files
MoviePilot/app/schemas/context.py

543 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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]]):
"""媒体、音乐、合集与人物的统一搜索结果列表。"""