mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-22 08:43:37 +08:00
feat: unify media recognition and music lifecycle
This commit is contained in:
@@ -3,39 +3,22 @@
|
||||
from typing import Any, Optional
|
||||
|
||||
from app.core.context import (
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
MUSIC_ENTITY_ARTIST,
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
MusicAlbumInfo,
|
||||
MusicArtistInfo,
|
||||
MusicInfo,
|
||||
)
|
||||
from app.schemas.types import media_type_to_agent
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_TYPES,
|
||||
MUSIC_SUBSCRIBABLE_TYPES,
|
||||
media_type_to_agent,
|
||||
)
|
||||
from app.utils.media import normalize_music_type
|
||||
|
||||
|
||||
MUSIC_ENTITY_TYPES = frozenset({
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
MUSIC_ENTITY_ARTIST,
|
||||
})
|
||||
MUSIC_SUBSCRIBABLE_TYPES = frozenset({
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
})
|
||||
MUSIC_TRACK_PREVIEW_LIMIT = 100
|
||||
MUSIC_RELEASE_PREVIEW_LIMIT = 20
|
||||
|
||||
|
||||
def normalize_music_type(
|
||||
value: Optional[str],
|
||||
*,
|
||||
allow_artist: bool = True,
|
||||
) -> Optional[str]:
|
||||
"""规范化音乐实体类型,非法值返回 None。"""
|
||||
normalized = str(value or "").strip().lower()
|
||||
allowed = MUSIC_ENTITY_TYPES if allow_artist else MUSIC_SUBSCRIBABLE_TYPES
|
||||
return normalized if normalized in allowed else None
|
||||
|
||||
|
||||
def simplify_music_info(info: MusicInfo) -> dict[str, Any]:
|
||||
"""精简音乐列表项,同时保留订阅和下载所需的稳定身份。"""
|
||||
payload = {
|
||||
|
||||
@@ -9,7 +9,7 @@ from app.agent.tools.tags import ToolTag
|
||||
from app.chain.subscribe import SubscribeChain
|
||||
from app.db.user_oper import UserOper
|
||||
from app.log import logger
|
||||
from app.schemas.types import MediaType, MessageChannel
|
||||
from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaType, MessageChannel
|
||||
from ._music_utils import normalize_music_type
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ class AddSubscribeTool(MoviePilotTool):
|
||||
if sid:
|
||||
display_year = f" ({year})" if year else ""
|
||||
music_label = (
|
||||
"专辑" if normalized_music_type == "album" else "单曲"
|
||||
"专辑" if normalized_music_type == MUSIC_ENTITY_ALBUM else "单曲"
|
||||
) if normalized_music_type else ""
|
||||
if message and "已存在" in message:
|
||||
result_msg = f"{music_label}订阅已存在:{title}{display_year}"
|
||||
|
||||
@@ -15,7 +15,12 @@ from app.modules.listenbrainz import (
|
||||
LISTENBRAINZ_FRESH_MAX_DAYS,
|
||||
LISTENBRAINZ_FRESH_SORTS,
|
||||
)
|
||||
from app.schemas.types import MediaType, media_type_to_agent
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
MediaType,
|
||||
media_type_to_agent,
|
||||
)
|
||||
from ._music_utils import normalize_music_type, simplify_music_info
|
||||
|
||||
|
||||
@@ -176,10 +181,10 @@ class GetRecommendationsTool(MoviePilotTool):
|
||||
sort_by=sort_by,
|
||||
min_listen_count=max(0, min_listen_count or 0),
|
||||
with_cover=bool(with_cover),
|
||||
entity=normalized_music_type or "recording",
|
||||
entity=normalized_music_type or MUSIC_ENTITY_RECORDING,
|
||||
)
|
||||
else:
|
||||
if normalized_music_type and normalized_music_type != "album":
|
||||
if normalized_music_type and normalized_music_type != MUSIC_ENTITY_ALBUM:
|
||||
return "错误:ListenBrainz 新发行结果只支持 music_type='album'"
|
||||
if fresh_sort not in LISTENBRAINZ_FRESH_SORTS:
|
||||
return f"错误:无效的新发行排序 '{fresh_sort}'"
|
||||
|
||||
@@ -11,7 +11,7 @@ from app.chain.download import DownloadChain
|
||||
from app.db.downloadhistory_oper import DownloadHistoryOper
|
||||
from app.log import logger
|
||||
from app.schemas import DownloaderTorrent
|
||||
from app.schemas.types import TorrentQueryStatus, media_type_to_agent
|
||||
from app.schemas.types import MUSIC_ENTITY_RECORDING, TorrentQueryStatus, media_type_to_agent
|
||||
|
||||
|
||||
class QueryDownloadTasksInput(BaseModel):
|
||||
@@ -124,7 +124,7 @@ class QueryDownloadTasksTool(MoviePilotTool):
|
||||
music_media = music_note.get("media") or {}
|
||||
if media_type_to_agent(history.type) == "music":
|
||||
media_payload.update({
|
||||
"music_type": music_media.get("music_type") or "recording",
|
||||
"music_type": music_media.get("music_type") or MUSIC_ENTITY_RECORDING,
|
||||
"artists": music_media.get("artists") or [],
|
||||
"album": music_media.get("album"),
|
||||
"album_id": music_media.get("album_id"),
|
||||
|
||||
@@ -10,10 +10,9 @@ from pydantic import BaseModel, Field
|
||||
from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
from app.chain.mediaserver import MediaServerChain
|
||||
from app.core.context import MUSIC_ENTITY_ALBUM
|
||||
from app.helper.mediaserver import MediaServerHelper
|
||||
from app.log import logger
|
||||
from app.schemas.types import MediaType, media_type_to_agent
|
||||
from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaType, media_type_to_agent
|
||||
from ._music_utils import normalize_music_type
|
||||
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.music import MusicChain
|
||||
from app.core.context import (
|
||||
from app.log import logger
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
MUSIC_ENTITY_ARTIST,
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
MediaType,
|
||||
)
|
||||
from app.log import logger
|
||||
from app.schemas.types import MediaType
|
||||
from ._music_utils import (
|
||||
normalize_music_type,
|
||||
simplify_music_album,
|
||||
@@ -200,6 +200,7 @@ class QueryMediaDetailTool(MoviePilotTool):
|
||||
source=media_source,
|
||||
mediaid=media_id,
|
||||
mtype=MediaType.MUSIC,
|
||||
music_type=normalized_music_type,
|
||||
)
|
||||
if (
|
||||
not mediainfo
|
||||
|
||||
@@ -11,7 +11,7 @@ from app.agent.tools.tags import ToolTag
|
||||
from app.core.context import MediaInfo
|
||||
from app.helper.server import MoviePilotServerHelper
|
||||
from app.log import logger
|
||||
from app.schemas.types import MediaType, media_type_to_agent
|
||||
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaType, media_type_to_agent
|
||||
from ._music_utils import normalize_music_type
|
||||
|
||||
MAX_PAGE_SIZE = 50
|
||||
@@ -133,7 +133,7 @@ class QueryPopularSubscribesTool(MoviePilotTool):
|
||||
raw_type = str(sub.get("type") or "").strip().lower()
|
||||
if raw_type in ["music", "音乐"]:
|
||||
sub_music_type = normalize_music_type(
|
||||
sub.get("music_type") or "recording",
|
||||
sub.get("music_type") or MUSIC_ENTITY_RECORDING,
|
||||
allow_artist=False,
|
||||
)
|
||||
if not sub_music_type:
|
||||
|
||||
@@ -9,7 +9,7 @@ from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
from app.db.subscribehistory_oper import SubscribeHistoryOper
|
||||
from app.log import logger
|
||||
from app.schemas.types import MediaType, media_type_to_agent
|
||||
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaType, media_type_to_agent
|
||||
from ._music_utils import normalize_music_type
|
||||
|
||||
PAGE_SIZE = 20
|
||||
@@ -197,7 +197,7 @@ class QuerySubscribeHistoryTool(MoviePilotTool):
|
||||
return True
|
||||
if media_type_to_agent(getattr(record, "type", None)) != "music":
|
||||
return False
|
||||
return (getattr(record, "music_type", None) or "recording") == music_type
|
||||
return (getattr(record, "music_type", None) or MUSIC_ENTITY_RECORDING) == music_type
|
||||
|
||||
@staticmethod
|
||||
def _simplify_records(records) -> list:
|
||||
@@ -217,7 +217,7 @@ class QuerySubscribeHistoryTool(MoviePilotTool):
|
||||
"media_source": record.media_source,
|
||||
"media_id": record.media_id,
|
||||
"music_type": getattr(record, "music_type", None) or (
|
||||
"recording" if media_type_to_agent(record.type) == "music" else None
|
||||
MUSIC_ENTITY_RECORDING if media_type_to_agent(record.type) == "music" else None
|
||||
),
|
||||
"total_tracks": getattr(record, "total_tracks", None),
|
||||
"poster": record.poster,
|
||||
|
||||
@@ -9,7 +9,7 @@ from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
from app.helper.server import MoviePilotServerHelper
|
||||
from app.log import logger
|
||||
from app.schemas.types import media_type_to_agent
|
||||
from app.schemas.types import MUSIC_ENTITY_RECORDING, media_type_to_agent
|
||||
from ._music_utils import normalize_music_type
|
||||
|
||||
MAX_PAGE_SIZE = 50
|
||||
@@ -97,7 +97,7 @@ class QuerySubscribeSharesTool(MoviePilotTool):
|
||||
normalized_music_type = None
|
||||
if normalized_type == "music":
|
||||
normalized_music_type = normalize_music_type(
|
||||
share.get("music_type") or "recording",
|
||||
share.get("music_type") or MUSIC_ENTITY_RECORDING,
|
||||
allow_artist=False,
|
||||
)
|
||||
if not normalized_music_type:
|
||||
|
||||
@@ -10,7 +10,7 @@ from app.agent.tools.tags import ToolTag
|
||||
from app.db.subscribe_oper import SubscribeOper
|
||||
from app.log import logger
|
||||
from app.schemas.subscribe import Subscribe as SubscribeSchema
|
||||
from app.schemas.types import MediaType, media_type_to_agent
|
||||
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaType, media_type_to_agent
|
||||
from ._music_utils import normalize_music_type
|
||||
|
||||
PAGE_SIZE = 100
|
||||
@@ -190,7 +190,7 @@ class QuerySubscribesTool(MoviePilotTool):
|
||||
if media_id is not None and sub.media_id != media_id:
|
||||
continue
|
||||
if normalized_music_type:
|
||||
sub_music_type = sub.music_type or "recording"
|
||||
sub_music_type = sub.music_type or MUSIC_ENTITY_RECORDING
|
||||
if sub_music_type != normalized_music_type:
|
||||
continue
|
||||
filtered_subscribes.append(sub)
|
||||
@@ -217,7 +217,7 @@ class QuerySubscribesTool(MoviePilotTool):
|
||||
payload["manual_total_episode"] = subscribe.manual_total_episode or 0
|
||||
payload["type"] = media_type_to_agent(payload.get("type"))
|
||||
if payload["type"] == "music" and not payload.get("music_type"):
|
||||
payload["music_type"] = "recording"
|
||||
payload["music_type"] = MUSIC_ENTITY_RECORDING
|
||||
full_subscribes.append(payload)
|
||||
result_json = json.dumps(full_subscribes, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
@@ -9,12 +9,10 @@ from pydantic import BaseModel, Field
|
||||
from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.music import MusicChain
|
||||
from app.core.config import settings
|
||||
from app.core.context import MUSIC_ENTITY_ALBUM, MUSIC_ENTITY_ARTIST
|
||||
from app.log import logger
|
||||
from app.schemas import FileItem
|
||||
from app.schemas.types import MediaType, media_type_to_agent
|
||||
from app.schemas.types import MUSIC_ENTITY_ARTIST, MediaType, media_type_to_agent
|
||||
from ._music_utils import normalize_music_type, simplify_music_info
|
||||
|
||||
|
||||
@@ -180,18 +178,16 @@ class ScrapeMetadataTool(MoviePilotTool):
|
||||
|
||||
mediainfo = None
|
||||
if media_source and media_id:
|
||||
if normalized_music_type == MUSIC_ENTITY_ALBUM:
|
||||
album_info = await MusicChain().async_album(
|
||||
source=media_source,
|
||||
media_id=media_id,
|
||||
)
|
||||
mediainfo = album_info.to_music_info() if album_info else None
|
||||
else:
|
||||
mediainfo = await media_chain.async_recognize_media(
|
||||
source=media_source,
|
||||
mediaid=media_id,
|
||||
mtype=MediaType.MUSIC,
|
||||
)
|
||||
recognize_kwargs = {
|
||||
"source": media_source,
|
||||
"mediaid": media_id,
|
||||
"mtype": MediaType.MUSIC,
|
||||
}
|
||||
if normalized_music_type:
|
||||
recognize_kwargs["music_type"] = normalized_music_type
|
||||
mediainfo = await media_chain.async_recognize_media(
|
||||
**recognize_kwargs
|
||||
)
|
||||
if not mediainfo:
|
||||
return json.dumps({
|
||||
"success": False,
|
||||
|
||||
@@ -126,18 +126,11 @@ class SearchTorrentsTool(MoviePilotTool):
|
||||
source=media_source,
|
||||
mediaid=media_id,
|
||||
mtype=media_type_enum,
|
||||
music_type=normalized_music_type,
|
||||
area=area or "title",
|
||||
sites=sites,
|
||||
cache_local=False,
|
||||
)
|
||||
if normalized_music_type:
|
||||
filtered_torrents = [
|
||||
context
|
||||
for context in filtered_torrents or []
|
||||
if getattr(context.media_info, "music_type", None)
|
||||
== normalized_music_type
|
||||
]
|
||||
|
||||
# 获取站点信息
|
||||
all_indexers = await SitesHelper().async_get_indexers()
|
||||
all_sites = [{"id": indexer.get("id"), "name": indexer.get("name")} for indexer in (all_indexers or [])]
|
||||
|
||||
@@ -7,9 +7,9 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
from app.core.context import MUSIC_ENTITY_ALBUM, MUSIC_ENTITY_RECORDING
|
||||
from app.log import logger
|
||||
from app.schemas import FileItem, MediaType
|
||||
from app.schemas.types import MUSIC_ENTITY_ALBUM, MUSIC_ENTITY_RECORDING
|
||||
from ._music_utils import normalize_music_type
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user