feat(v3): add music automation workflow

This commit is contained in:
jxxghp
2026-08-07 21:50:22 +08:00
parent c6bd396794
commit 05d15a04f5
65 changed files with 3505 additions and 257 deletions
+1
View File
@@ -8,6 +8,7 @@ from .file import *
from .history import *
from .mediaserver import *
from .message import *
from .music import *
from .monitoring import *
from .plugin import *
from .response import *
+6 -4
View File
@@ -2,6 +2,8 @@ from typing import Optional, Dict, List, Union, Any
from pydantic import BaseModel, Field
from app.schemas.music import MusicInfo, MusicMeta
class MetaInfo(BaseModel):
"""
@@ -15,7 +17,7 @@ class MetaInfo(BaseModel):
title: Optional[str] = None
# 副标题
subtitle: Optional[str] = None
# 类型 电影、电视剧
# 类型 电影、电视剧、音乐
type: Optional[str] = None
# 名称
name: Optional[str] = None
@@ -252,7 +254,7 @@ class TorrentInfo(BaseModel):
labels: Optional[list] = Field(default_factory=list)
# 种子优先级
pri_order: Optional[int] = 0
# 种子分类 电影/电视剧
# 种子分类 电影/电视剧/音乐
category: Optional[str] = None
# 促销
volume_factor: Optional[str] = None
@@ -319,9 +321,9 @@ class Context(BaseModel):
上下文
"""
# 元数据
meta_info: Optional[Union[MetaInfo, Any]] = None
meta_info: Optional[Union[MusicMeta, MetaInfo, Any]] = None
# 媒体信息
media_info: Optional[Union[MediaInfo, Any]] = None
media_info: Optional[Union[MusicInfo, MediaInfo, Any]] = None
# 种子信息
torrent_info: Optional[TorrentInfo] = None
# 候选资源来源:rss、spider、search、unknown
+1 -1
View File
@@ -12,7 +12,7 @@ class DownloadHistory(BaseModel):
id: int
# 保存路程
path: Optional[str] = None
# 类型:电影、电视剧
# 类型:电影、电视剧、音乐
type: Optional[str] = None
# 标题
title: Optional[str] = None
+70
View File
@@ -0,0 +1,70 @@
from typing import Any, Literal, Optional
from pydantic import BaseModel, Field
class MusicMeta(BaseModel):
"""音乐名称及音频文件解析结果。"""
type: Literal["音乐"] = "音乐"
org_string: Optional[str] = None
title: Optional[str] = None
artists: list[str] = Field(default_factory=list)
artist: Optional[str] = None
album: Optional[str] = None
album_artist: Optional[str] = None
year: Optional[int] = None
disc_number: Optional[int] = None
track_number: Optional[int] = None
total_discs: Optional[int] = None
total_tracks: Optional[int] = None
version: Optional[str] = None
audio_format: Optional[str] = None
bit_depth: Optional[int] = None
sample_rate: Optional[int] = None
bitrate: Optional[int] = None
duration: Optional[int] = None
isrc: Optional[str] = None
media_source: Optional[str] = None
media_id: Optional[str] = None
class MusicInfo(BaseModel):
"""标准化音乐元数据信息。"""
type: Literal["音乐"] = "音乐"
source: Optional[str] = None
media_id: Optional[str] = None
title: Optional[str] = None
artists: list[str] = Field(default_factory=list)
artist: Optional[str] = None
album: Optional[str] = None
album_artist: Optional[str] = None
year: Optional[int] = None
release_date: Optional[str] = None
disc_number: Optional[int] = None
track_number: Optional[int] = None
total_tracks: Optional[int] = None
duration: Optional[int] = None
isrc: Optional[str] = None
cover_url: Optional[str] = None
lyrics: Optional[str] = None
version: Optional[str] = None
category: Optional[str] = ""
names: list[str] = Field(default_factory=list)
detail_link: Optional[str] = None
listen_count: Optional[int] = None
raw_data: dict[str, Any] = Field(default_factory=dict)
title_year: Optional[str] = None
poster_path: Optional[str] = None
backdrop_path: Optional[str] = None
mediaid_prefix: Optional[str] = None
overview: Optional[str] = None
vote_average: float = 0.0
class MusicRecognizeRequest(BaseModel):
"""音乐元数据详情识别请求。"""
source: str
media_id: str
+4 -3
View File
@@ -1,9 +1,10 @@
from pathlib import Path
from typing import Any, Callable, List, Optional
from typing import Any, Callable, List, Optional, Union
from pydantic import BaseModel, Field
from app.schemas.context import MetaInfo, MediaInfo
from app.schemas.music import MusicInfo, MusicMeta
from app.schemas.file import FileItem
from app.schemas.history import DownloadHistory
from app.schemas.system import TransferDirectoryConf
@@ -96,7 +97,7 @@ class TransferJobTask(BaseModel):
文件整理作业任务
"""
fileitem: Optional[FileItem] = None
meta: Optional[MetaInfo] = None
meta: Optional[Union[MusicMeta, MetaInfo]] = None
state: Optional[str] = None
downloader: Optional[str] = None
download_hash: Optional[str] = None
@@ -106,7 +107,7 @@ class TransferJob(BaseModel):
"""
文件整理作业
"""
media: Optional[MediaInfo] = None
media: Optional[Union[MusicInfo, MediaInfo]] = None
season: Optional[int] = None
tasks: Optional[List[TransferJobTask]] = Field(default_factory=list)
+18 -5
View File
@@ -6,22 +6,31 @@ from typing import Optional
class MediaType(Enum):
MOVIE = '电影'
TV = '电视剧'
MUSIC = '音乐'
COLLECTION = '系列'
UNKNOWN = '未知'
@staticmethod
def from_agent(key: str) -> Optional["MediaType"]:
"""'movie' -> MediaType.MOVIE, 'tv' -> MediaType.TV, 否则 None"""
_map = {"movie": MediaType.MOVIE, "tv": MediaType.TV}
"""将 Agent 媒体类型转换为 MediaType。"""
_map = {
"movie": MediaType.MOVIE,
"tv": MediaType.TV,
"music": MediaType.MUSIC,
}
return _map.get(key.strip().lower() if key else "")
def to_agent(self) -> str:
"""MediaType.MOVIE -> 'movie', MediaType.TV -> 'tv', 其他返回 .value"""
return {MediaType.MOVIE: "movie", MediaType.TV: "tv"}.get(self, self.value)
"""将 MediaType 转换为 Agent 使用的媒体类型。"""
return {
MediaType.MOVIE: "movie",
MediaType.TV: "tv",
MediaType.MUSIC: "music",
}.get(self, self.value)
def media_type_to_agent(value) -> Optional[str]:
"""将 MediaType 枚举或中文字符串统一转为 'movie'/'tv'"""
"""将 MediaType 枚举或字符串统一转Agent 媒体类型。"""
if isinstance(value, MediaType):
return value.to_agent()
if isinstance(value, str):
@@ -397,6 +406,8 @@ class MediaRecognizeType(Enum):
Bangumi = "Bangumi"
# AniList
AniList = "AniList"
# MusicBrainz
MusicBrainz = "MusicBrainz"
# 用户配置Key字典
@@ -448,6 +459,8 @@ class OtherModulesType(Enum):
PostgreSQL = "PostgreSQL"
# Redis
Redis = "Redis"
# ListenBrainz
ListenBrainz = "ListenBrainz"
class NameValueEnum(Enum):