feat(media): support extensible media sources

This commit is contained in:
jxxghp
2026-08-13 13:26:17 +08:00
parent f80562915d
commit b4863a6550
28 changed files with 460 additions and 132 deletions
+6 -6
View File
@@ -482,12 +482,12 @@ class DiscoverMediaSource(BaseModel):
探索媒体数据源的基类。
``mediaid_prefix`` 是既有插件与前端标签使用的稳定标识;
``media_source`` 是新的规范媒体来源。模型同时输出两者,并在输入时互相补齐,
以兼容尚未升级的已安装插件。
``media_source`` 是新的规范媒体来源,可以是内置常量或插件扩展成员。模型同时
输出两者,并在输入时互相补齐,以兼容尚未升级的已安装插件。
"""
name: str = Field(..., description="数据源名称")
media_source: MediaSource = Field(..., description="媒体来源枚举")
media_source: MediaSource = Field(..., description="内置或插件扩展媒体来源")
mediaid_prefix: str = Field(..., description="兼容插件使用的媒体ID前缀")
api_path: str = Field(..., description="媒体数据源API地址")
filter_params: Optional[Dict[str, JsonData]] = Field(
@@ -517,7 +517,7 @@ class DiscoverMediaSource(BaseModel):
@staticmethod
def _media_source_from_prefix(mediaid_prefix: str) -> MediaSource:
"""将旧插件使用的历史前缀映射为规范媒体来源枚举"""
"""将旧插件前缀映射为内置或插件扩展媒体来源。"""
aliases = {
"mangguo": MediaSource.MangoTV,
"tencentvideo": MediaSource.TencentVideo,
@@ -573,9 +573,9 @@ class MediaRecognizeConvertEventData(RequiredMediaIdentityMixin, ChainEventData)
Attributes:
# 输入参数
media_source (MediaSource): 输入媒体来源
media_source (MediaSource): 输入内置或插件扩展媒体来源
media_id (str): 数据源原生 ID
target_media_source (MediaSource): 需要转换到的目标媒体来源
target_media_source (MediaSource): 需要转换到的内置或插件扩展媒体来源
# 输出参数
media_dict (dict): TheMovieDb/豆瓣的媒体数据
+2 -2
View File
@@ -4,7 +4,7 @@ from app.schemas.types import MediaSource
class OptionalMediaIdentityMixin:
"""为可选媒体身份模型统一校验来源枚举与原生 ID 的成对约束。"""
"""为可选媒体身份模型统一校验内置或插件来源与原生 ID 的成对约束。"""
@model_validator(mode="before")
@classmethod
@@ -52,7 +52,7 @@ class OptionalMediaIdentityMixin:
class RequiredMediaIdentityMixin:
"""为必填媒体身份模型统一校验来源枚举与原生 ID。"""
"""为必填媒体身份模型统一校验内置或插件来源与原生 ID。"""
@model_validator(mode="after")
def _validate_required_media_identity(self):
+1 -1
View File
@@ -224,7 +224,7 @@ class EpisodeFormatRecommendItem(BaseModel):
class ManualTransferItem(OptionalMediaIdentityMixin, BaseModel):
"""手动整理请求,媒体身份接受来源枚举与原生 ID。"""
"""手动整理请求,媒体身份接受内置或插件来源与原生 ID。"""
# 文件项
fileitem: FileItem = None
+47 -2
View File
@@ -1,6 +1,10 @@
import re
from enum import Enum
from typing import Literal, Optional, Tuple, Union
from pydantic import GetJsonSchemaHandler
from pydantic_core import CoreSchema
# 音乐实体命名空间由公共类型模块统一持有,避免模型、接口和工具层重复定义。
MUSIC_ENTITY_RECORDING = "recording"
@@ -46,8 +50,20 @@ class MediaType(Enum):
}.get(self, self.value)
MEDIA_SOURCE_IDENTIFIER_PATTERN = r"^[a-z][a-z0-9._-]{0,63}$"
_MEDIA_SOURCE_IDENTIFIER_RE = re.compile(MEDIA_SOURCE_IDENTIFIER_PATTERN)
_MEDIA_SOURCE_VALUE_ALIASES = {
"tmdb": "themoviedb",
"audio_db": "theaudiodb",
"douban_music": "doubanmusic",
"mango_tv": "mangguodiscover",
"migu_video": "migu",
"tencent_video": "tencentvideodiscover",
}
class MediaSource(str, Enum):
"""媒体主身份的数据来源。"""
"""媒体主身份的数据来源,内置来源为常量,插件来源为动态扩展成员"""
TMDB = "themoviedb"
Douban = "douban"
@@ -67,8 +83,37 @@ class MediaSource(str, Enum):
"""返回可直接用于 API 和数据库的规范值。"""
return self.value
@classmethod
def _missing_(cls, value: object) -> Optional["MediaSource"]:
"""将合法插件来源标识解析为动态枚举成员,并规范化内置别名。"""
if not isinstance(value, str):
return None
normalized = value.strip().casefold()
normalized = _MEDIA_SOURCE_VALUE_ALIASES.get(normalized, normalized)
known_member = cls._value2member_map_.get(normalized)
if known_member:
return known_member
if not _MEDIA_SOURCE_IDENTIFIER_RE.fullmatch(normalized):
return None
member = str.__new__(cls, normalized)
member._name_ = normalized
member._value_ = normalized
cls._value2member_map_.setdefault(normalized, member)
return cls._value2member_map_[normalized]
# 搜索可以选择一个或多个来源,但集合中的每一项都必须是固定枚举。
@classmethod
def __get_pydantic_json_schema__(
cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler,
) -> dict:
"""在 OpenAPI 中声明可扩展标识格式,避免把内置成员误写成完整白名单。"""
schema = handler(core_schema)
schema.pop("enum", None)
schema["pattern"] = MEDIA_SOURCE_IDENTIFIER_PATTERN
schema["examples"] = [source.value for source in cls]
return schema
# 搜索可以选择一个或多个内置或插件扩展来源。
MediaSourceSelection = Union[MediaSource, Tuple[MediaSource, ...]]