refactor(media): unify media identity and chain responsibilities

This commit is contained in:
jxxghp
2026-08-12 18:31:09 +08:00
parent cbb969e0ff
commit ca32922a7f
155 changed files with 8371 additions and 4526 deletions

View File

@@ -5,8 +5,8 @@ from fastapi import APIRouter, Depends, Body
from app import schemas
from app.chain.download import DownloadChain
from app.chain.media import MediaChain
from app.chain.music import MusicChain
from app.core.context import Context, MediaInfo, MusicInfo, SubtitleInfo, TorrentInfo
from app.core.meta import MetaMusic
from app.core.metainfo import MetaInfo
from app.core.security import verify_token
from app.db.models.user import User
@@ -75,7 +75,7 @@ def download(
"""
if isinstance(media_in, schemas.MusicInfo):
mediainfo = MusicInfo.from_dict(media_in.model_dump())
metainfo = MusicChain.to_meta(mediainfo)
metainfo = MetaMusic.from_music_info(mediainfo)
metainfo.org_string = torrent_in.title
else:
metainfo = MetaInfo(title=torrent_in.title, subtitle=torrent_in.description)
@@ -142,7 +142,7 @@ def add(
normalized_music_type = MUSIC_ENTITY_RECORDING
# 元数据
metainfo = (
MusicChain.parse_query(torrent_in.title)
MetaMusic.parse_query(torrent_in.title)
if is_music
else MetaInfo(title=torrent_in.title, subtitle=torrent_in.description)
)

View File

@@ -2,11 +2,12 @@ from pathlib import Path
from typing import Annotated, Any, List, Optional, Union
from uuid import UUID
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, Query
from pydantic import BeforeValidator
from app import schemas
from app.chain.media import MediaChain
from app.chain.music import MusicChain
from app.chain.scraping import ScrapingChain
from app.chain.tmdb import TmdbChain
from app.core.config import settings
from app.core.context import Context, MusicInfo
@@ -20,28 +21,64 @@ from app.schemas.category import CategoryConfig
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaSource
from app.utils.media import (
is_music_media_source,
normalize_media_source,
normalize_music_type,
parse_media_source_selection,
resolve_media_identity,
)
router = APIRouter()
def _split_media_source_query(value: object) -> tuple[str, ...]:
"""展开重复或逗号分隔的来源参数,并在枚举校验前规范历史别名。"""
if value in (None, ""):
return ()
values = value if isinstance(value, (list, tuple)) else (value,)
sources = tuple(
source.strip()
for item in values
for source in str(item).split(",")
if source.strip()
)
return tuple(
normalized.value if (normalized := normalize_media_source(source)) else source
for source in sources
)
MediaSourceQuery = Annotated[
tuple[MediaSource, ...],
BeforeValidator(_split_media_source_query),
Query(),
]
def _is_valid_source_media_id(
media_source: Optional[MediaSource], media_id: str,
) -> bool:
"""按媒体数据源校验原生 ID并兼容豆瓣音乐的曲目复合 ID。"""
if media_source == MediaSource.MusicBrainz:
normalized_source, normalized_media_id = resolve_media_identity(
media_source=media_source,
media_id=media_id,
)
if not normalized_source or not normalized_media_id:
return False
if normalized_source == MediaSource.MusicBrainz:
try:
UUID(media_id)
UUID(normalized_media_id)
return True
except (TypeError, ValueError):
return False
if media_source == MediaSource.DoubanMusic and ":" in media_id:
album_id, track_number = media_id.split(":", 1)
if normalized_source == MediaSource.DoubanMusic and ":" in normalized_media_id:
album_id, track_number = normalized_media_id.split(":", 1)
return album_id.isdigit() and track_number.isdigit()
if media_source == MediaSource.IMDb:
return media_id.startswith("tt") and media_id[2:].isdigit()
return bool(media_id.strip())
if normalized_source == MediaSource.IMDb:
return (
normalized_media_id.startswith("tt")
and normalized_media_id[2:].isdigit()
)
return True
def _build_recognize_metainfo(
@@ -132,7 +169,7 @@ async def recognize(
metainfo = _build_recognize_metainfo(title, subtitle, custom_words)
# 显式音乐来源需要按音乐元数据解析,避免名称测试误入影视识别。
if is_music_media_source(media_source) and not isinstance(metainfo, MetaMusic):
metainfo = MusicChain.parse_query(title)
metainfo = MetaMusic.parse_query(title)
mediainfo = await MediaChain().async_recognize_by_meta(
metainfo,
media_source=media_source,
@@ -204,7 +241,7 @@ async def search(
type: Optional[str] = "media",
page: int = 1,
count: int = 8,
media_source: Optional[str] = None,
media_source: MediaSourceQuery = (),
_: schemas.TokenPayload = Depends(verify_token),
) -> Any:
"""
@@ -214,7 +251,7 @@ async def search(
:param type: 搜索类型,支持 media、music、collection、person
:param page: 页码
:param count: 每页数量
:param media_source: 请求级搜索数据源,支持逗号分隔
:param media_source: 请求级搜索数据源枚举;可重复传入,逗号格式仅用于兼容旧客户端
:param _: Token校验
:return: 搜索结果列表
"""
@@ -227,33 +264,43 @@ async def search(
return obj.get("media_source")
return obj.media_source
# 直接函数调用也可能绕过 FastAPI/Pydantic仅在该测试与内部兼容边界补一次规范化。
selected_sources = (
media_source
if isinstance(media_source, tuple)
and all(isinstance(source, MediaSource) for source in media_source)
else parse_media_source_selection(",".join(_split_media_source_query(media_source)))
)
selected_sources = tuple(dict.fromkeys(selected_sources))
source_selection = selected_sources or None
media_chain = MediaChain()
if type == "music" or is_music_media_source(media_source):
if type == "music" or any(is_music_media_source(source) for source in selected_sources):
# 音乐搜索统一入口,与影视搜索共用 /media/search
music_search_params = {"query": title, "limit": count}
# 未指定来源时保留既有调用契约,由 MusicChain 选择默认音乐源。
if media_source:
music_search_params["media_source"] = media_source
music_infos = await MusicChain().async_search(**music_search_params)
# 未指定来源时由 MediaChain 使用默认 MusicBrainz 来源。
if source_selection:
music_search_params["media_source"] = source_selection
music_infos = await media_chain.async_search_music(**music_search_params)
return [
info.to_dict()
for info in music_infos
] if music_infos else []
if type == "media":
_, medias = await media_chain.async_search(
title=title, media_source=media_source
title=title, media_source=source_selection
)
result = [media.to_dict() for media in medias] if medias else []
elif type == "collection":
collections = await media_chain.async_search_collections(
name=title, media_source=media_source
name=title, media_source=source_selection
)
result = (
[collection.to_dict() for collection in collections] if collections else []
)
else: # person
persons = await media_chain.async_search_persons(
name=title, media_source=media_source
name=title, media_source=source_selection
)
result = [person.model_dump() for person in persons] if persons else []
@@ -293,7 +340,10 @@ def scrape(
"""
if not fileitem or not fileitem.path:
return schemas.Response(success=False, message="刮削路径无效")
normalized_media_id = media_id.strip() if media_id else None
has_explicit_media_id = media_id is not None
normalized_media_id = str(media_id).strip() if has_explicit_media_id else None
if has_explicit_media_id and not normalized_media_id:
return schemas.Response(success=False, message="媒体ID格式无效")
if normalized_media_id and not media_source:
return schemas.Response(
success=False, message="指定媒体ID时必须同时指定媒体数据源"
@@ -329,7 +379,7 @@ def scrape(
)
if not music_info:
return schemas.Response(success=False, message="刮削失败,无法识别音乐信息")
success, message = MediaChain().scrape_music_metadata(
success, message = ScrapingChain().scrape_music_metadata(
fileitem=fileitem,
mediainfo=music_info,
overwrite=True,
@@ -366,7 +416,7 @@ def scrape(
if not Path(fileitem.path).exists():
return schemas.Response(success=False, message="刮削路径不存在")
# 手动刮削 (暂时使用同步版本,可以后续优化为异步)
chain.scrape_metadata(
ScrapingChain().scrape_metadata(
fileitem=fileitem,
meta=meta_info,
mediainfo=media_info,
@@ -420,7 +470,13 @@ async def group_seasons(
"""
查询剧集组季信息themoviedb
"""
return await TmdbChain().async_tmdb_group_seasons(group_id=episode_group)
_, normalized_group_id = resolve_media_identity(
media_source=MediaSource.TMDB,
media_id=episode_group,
)
if not normalized_group_id:
return []
return await TmdbChain().async_tmdb_group_seasons(group_id=normalized_group_id)
@router.get("/groups/{tmdbid}", summary="查询媒体剧集组", response_model=List[dict])
@@ -428,9 +484,15 @@ async def groups(tmdbid: int, _: schemas.TokenPayload = Depends(verify_token)) -
"""
查询媒体剧集组列表themoviedb
"""
mediainfo = await MediaChain().async_recognize_media(
media_source, media_id = resolve_media_identity(
media_source=MediaSource.TMDB,
media_id=str(tmdbid),
media_id=tmdbid,
)
if not media_source or not media_id:
return []
mediainfo = await MediaChain().async_recognize_media(
media_source=media_source,
media_id=media_id,
mtype=MediaType.TV,
)
if not mediainfo:
@@ -452,11 +514,15 @@ async def seasons(
"""
查询媒体季信息
"""
if media_source or media_id:
if not media_source or not media_id:
if media_source is not None or media_id is not None:
normalized_source, normalized_media_id = resolve_media_identity(
media_source=media_source,
media_id=media_id,
)
if not normalized_source or not normalized_media_id:
return []
if media_source == MediaSource.TMDB and media_id.isdigit():
tmdbid = int(media_id)
if normalized_source == MediaSource.TMDB and normalized_media_id.isdigit():
tmdbid = int(normalized_media_id)
seasons_info = await TmdbChain().async_tmdb_seasons(tmdbid=tmdbid)
if seasons_info:
if season is not None:
@@ -464,8 +530,8 @@ async def seasons(
return seasons_info
else:
mediainfo = await MediaChain().async_recognize_media(
media_source=media_source,
media_id=media_id,
media_source=normalized_source,
media_id=normalized_media_id,
mtype=MediaType.TV,
cache=False,
)
@@ -483,13 +549,16 @@ async def seasons(
obtain_images=False,
)
if mediainfo:
recognized_source, recognized_media_id = resolve_media_identity(
media=mediainfo
)
if (
mediainfo.media_source == MediaSource.TMDB
and mediainfo.media_id
and mediainfo.media_id.isdigit()
recognized_source == MediaSource.TMDB
and recognized_media_id
and recognized_media_id.isdigit()
):
seasons_info = await TmdbChain().async_tmdb_seasons(
tmdbid=int(mediainfo.media_id)
tmdbid=int(recognized_media_id)
)
if seasons_info:
if season is not None:
@@ -512,10 +581,16 @@ async def detail(
根据媒体来源和原生 ID 查询媒体信息type_name: 电影/电视剧
"""
mtype = MediaType(type_name)
mediachain = MediaChain()
mediainfo = await mediachain.async_recognize_media(
normalized_source, normalized_media_id = resolve_media_identity(
media_source=media_source,
media_id=media_id,
)
if not normalized_source or not normalized_media_id:
return schemas.MediaInfo()
mediachain = MediaChain()
mediainfo = await mediachain.async_recognize_media(
media_source=normalized_source,
media_id=normalized_media_id,
mtype=mtype,
)
# 识别

View File

@@ -4,8 +4,8 @@ from fastapi import APIRouter, Depends, HTTPException, Query
from app import schemas
from app.chain.media import MediaChain
from app.chain.music import MusicChain
from app.schemas.types import MediaType
from app.chain.recommend import RecommendChain
from app.schemas.types import MediaSource, MediaType
from app.core.context import MusicAlbumInfo, MusicArtistInfo, MusicInfo
from app.core.security import verify_token
from app.db.models.user import User
@@ -22,12 +22,12 @@ router = APIRouter()
CountParam = Annotated[int, Query(ge=1, le=100)]
PageParam = Annotated[int, Query(ge=1)]
MusicSourceParam = Annotated[
str,
Query(pattern="^(musicbrainz|theaudiodb|doubanmusic)$"),
MediaSource,
Query(),
]
MusicExploreSourceParam = Annotated[
str,
Query(pattern="^(musicbrainz|doubanmusic)$"),
MediaSource,
Query(),
]
MusicModeParam = Annotated[str, Query(pattern="^(chart|fresh)$")]
MusicEntityParam = Annotated[str, Query(pattern="^(recording|album)$")]
@@ -41,6 +41,29 @@ MusicAlbumTypeParam = Annotated[
Optional[str],
Query(pattern="^(album|single|ep|broadcast|other|compilation|soundtrack|live|remix)$"),
]
_MUSIC_DETAIL_SOURCES = frozenset({
MediaSource.MusicBrainz,
MediaSource.TheAudioDB,
MediaSource.DoubanMusic,
})
_MUSIC_EXPLORE_SOURCES = frozenset({
MediaSource.MusicBrainz,
MediaSource.DoubanMusic,
})
def _validate_music_source(
media_source: MediaSource,
allowed_sources: frozenset[MediaSource],
) -> MediaSource:
"""将 HTTP 或直接调用参数规范为音乐来源枚举,并拒绝不支持的来源。"""
try:
normalized_source = MediaSource(media_source)
except (TypeError, ValueError) as err:
raise HTTPException(status_code=422, detail="无效的媒体来源") from err
if normalized_source not in allowed_sources:
raise HTTPException(status_code=422, detail="该媒体来源不支持此音乐接口")
return normalized_source
def _serialize_music(info: MusicInfo) -> schemas.MusicInfo:
@@ -138,7 +161,7 @@ async def clear_music_recognition_cache(
async def explore_music(
page: PageParam = 1,
count: CountParam = 30,
media_source: MusicExploreSourceParam = "musicbrainz",
media_source: MusicExploreSourceParam = MediaSource.MusicBrainz,
mode: MusicModeParam = "chart",
entity: MusicEntityParam = "recording",
range_name: MusicRangeParam = "this_month",
@@ -154,9 +177,10 @@ async def explore_music(
_: schemas.TokenPayload = Depends(verify_token),
) -> list[schemas.MusicInfo]:
"""MusicBrainz 返回榜单或新发行,豆瓣音乐固定按官方标签分类浏览。"""
chain = MusicChain()
if media_source != "musicbrainz":
results = await chain.async_discover(
media_source = _validate_music_source(media_source, _MUSIC_EXPLORE_SOURCES)
chain = RecommendChain()
if media_source != MediaSource.MusicBrainz:
results = await chain.async_music_discover(
media_source=media_source,
page=page,
count=count,
@@ -166,7 +190,7 @@ async def explore_music(
sort=douban_sort,
)
elif mode == "fresh":
results = await chain.async_fresh_releases(
results = await chain.async_music_fresh_releases(
days=days,
sort=sort,
past=past,
@@ -176,7 +200,7 @@ async def explore_music(
with_cover=with_cover,
)
else:
results = await chain.async_chart(
results = await chain.async_music_chart(
range_name=range_name,
page=page,
count=count,
@@ -185,7 +209,7 @@ async def explore_music(
with_cover=with_cover,
entity=entity,
)
if media_source != "musicbrainz" and with_cover:
if media_source != MediaSource.MusicBrainz and with_cover:
results = [info for info in results if info.cover_url or info.poster_path]
return [_serialize_music(info) for info in results]
@@ -197,11 +221,14 @@ async def explore_music(
)
async def music_album(
album_id: str,
media_source: MusicSourceParam = "musicbrainz",
media_source: MusicSourceParam = MediaSource.MusicBrainz,
_: schemas.TokenPayload = Depends(verify_token),
) -> schemas.MusicAlbumInfo:
"""按专辑标准 ID 返回专辑详情、曲目列表和发行版本。"""
info = await MusicChain().async_album(media_source=media_source, media_id=album_id)
media_source = _validate_music_source(media_source, _MUSIC_DETAIL_SOURCES)
info = await MediaChain().async_get_music_album(
media_source=media_source, media_id=album_id
)
if not info:
raise HTTPException(status_code=404, detail="未识别到专辑信息")
return _serialize_album(info)
@@ -215,11 +242,12 @@ async def music_album(
async def music_album_related(
album_id: str,
count: CountParam = 24,
media_source: MusicSourceParam = "musicbrainz",
media_source: MusicSourceParam = MediaSource.MusicBrainz,
_: schemas.TokenPayload = Depends(verify_token),
) -> list[schemas.MusicInfo]:
"""按来源和专辑 ID 返回可继续浏览的关联专辑。"""
results = await MusicChain().async_album_related(
media_source = _validate_music_source(media_source, _MUSIC_DETAIL_SOURCES)
results = await MediaChain().async_get_music_album_related(
media_source=media_source,
media_id=album_id,
count=count,
@@ -237,11 +265,12 @@ async def music_artist_albums(
page: PageParam = 1,
count: CountParam = 30,
album_type: MusicAlbumTypeParam = None,
media_source: MusicSourceParam = "musicbrainz",
media_source: MusicSourceParam = MediaSource.MusicBrainz,
_: schemas.TokenPayload = Depends(verify_token),
) -> list[schemas.MusicInfo]:
"""按艺术家标准 ID 分页返回其专辑、EP 和单曲。"""
results = await MusicChain().async_artist_albums(
media_source = _validate_music_source(media_source, _MUSIC_DETAIL_SOURCES)
results = await MediaChain().async_get_music_artist_albums(
media_source=media_source,
media_id=artist_id,
page=page,
@@ -259,11 +288,12 @@ async def music_artist_albums(
async def music_artist_related(
artist_id: str,
count: CountParam = 24,
media_source: MusicSourceParam = "musicbrainz",
media_source: MusicSourceParam = MediaSource.MusicBrainz,
_: schemas.TokenPayload = Depends(verify_token),
) -> list[schemas.MusicArtistInfo]:
"""按艺术家关系返回可继续浏览的关联艺术家。"""
results = await MusicChain().async_artist_related(
media_source = _validate_music_source(media_source, _MUSIC_DETAIL_SOURCES)
results = await MediaChain().async_get_music_artist_related(
media_source=media_source,
media_id=artist_id,
count=count,
@@ -278,11 +308,14 @@ async def music_artist_related(
)
async def music_artist(
artist_id: str,
media_source: MusicSourceParam = "musicbrainz",
media_source: MusicSourceParam = MediaSource.MusicBrainz,
_: schemas.TokenPayload = Depends(verify_token),
) -> schemas.MusicArtistInfo:
"""按艺术家标准 ID 返回艺术家详情。"""
info = await MusicChain().async_artist(media_source=media_source, media_id=artist_id)
media_source = _validate_music_source(media_source, _MUSIC_DETAIL_SOURCES)
info = await MediaChain().async_get_music_artist(
media_source=media_source, media_id=artist_id
)
if not info:
raise HTTPException(status_code=404, detail="未识别到艺术家信息")
return _serialize_artist(info)

View File

@@ -13,7 +13,7 @@ from app.core.security import verify_resource_token, verify_token
from app.helper.locale import LocaleHelper
from app.log import logger
from app.schemas.types import MediaSource, MediaType
from app.utils.media import normalize_music_type
from app.utils.media import normalize_music_type, resolve_media_identity
from app.utils.security import SecurityUtils
router = APIRouter()
@@ -59,9 +59,12 @@ async def _resolve_media_search_params(
music_type: Optional[str] = None,
) -> tuple[Optional[dict], str]:
"""校验统一媒体身份并构造 SearchChain 精确搜索参数。"""
normalized_media_id = str(media_id or "").strip()
if not normalized_media_id:
return None, "媒体 ID 不能为空"
normalized_source, normalized_media_id = resolve_media_identity(
media_source=media_source,
media_id=media_id,
)
if not normalized_source or not normalized_media_id:
return None, "媒体ID格式无效"
normalized_music_type = None
if music_type:
normalized_music_type = normalize_music_type(music_type, allow_artist=False)
@@ -71,7 +74,7 @@ async def _resolve_media_search_params(
return None, "music_type 仅能用于音乐资源搜索"
params = {
"media_source": media_source,
"media_source": normalized_source,
"media_id": normalized_media_id,
}
if normalized_music_type:
@@ -368,7 +371,6 @@ async def search_by_id_stream(
media_type = _parse_media_type(mtype)
media_season = int(season) if season else None
site_list = _parse_site_list(sites)
search_chain = SearchChain()
async def event_source():
"""解析媒体身份并输出精确搜索流事件。"""
@@ -381,7 +383,7 @@ async def search_by_id_stream(
if not search_params:
yield {"type": "error", "success": False, "message": message}
return
torrents = search_chain.async_search_by_id_stream(
torrents = SearchChain().async_search_by_id_stream(
**search_params,
mtype=media_type,
area=area,
@@ -551,7 +553,6 @@ async def _build_subtitle_search_source(
media_season = int(season) if season else None
media_episode = int(episode) if episode else None
site_list = _parse_site_list(sites)
search_chain = SearchChain()
def call_search(**kwargs):
"""
@@ -576,6 +577,7 @@ async def _build_subtitle_search_source(
)
if not search_params:
return None, message
search_chain = SearchChain()
return call_search(**search_params), ""

View File

@@ -30,7 +30,7 @@ from app.schemas.types import (
EventType,
SystemConfigKey,
)
from app.utils.media import normalize_media_source
from app.utils.media import normalize_media_source, resolve_media_identity
router = APIRouter()
@@ -211,6 +211,26 @@ async def create_subscribe(
else:
title = None
subscribe_dict = subscribe_in.to_public_write_payload()
identity_fields = {"media_source", "media_id"}.intersection(
subscribe_in.model_fields_set
)
if identity_fields:
media_source, media_id = resolve_media_identity(
media_source=subscribe_in.media_source,
media_id=subscribe_in.media_id,
)
if media_source and media_id:
subscribe_dict["media_source"] = media_source
subscribe_dict["media_id"] = media_id
elif subscribe_in.media_source is None and subscribe_in.media_id is None:
# 完整空对表示订阅暂无可用身份,与只提交其中一个字段语义不同。
subscribe_dict["media_source"] = None
subscribe_dict["media_id"] = None
else:
return schemas.Response(
success=False,
message="新增订阅时必须同时提供有效的 media_source 和 media_id",
)
subscribe_dict["username"] = current_user.name
sid, message = await SubscribeChain().async_add(
mtype=mtype,
@@ -236,7 +256,27 @@ async def update_subscribe(
if not subscribe:
return schemas.Response(success=False, message="订阅不存在")
old_subscribe_dict = subscribe.to_dict()
subscribe_dict = subscribe_in.to_public_write_payload()
subscribe_dict = subscribe_in.to_public_write_payload(exclude_unset=True)
identity_fields = {"media_source", "media_id"}.intersection(
subscribe_in.model_fields_set
)
if identity_fields:
media_source, media_id = resolve_media_identity(
media_source=subscribe_in.media_source,
media_id=subscribe_in.media_id,
)
if media_source and media_id:
subscribe_dict["media_source"] = media_source
subscribe_dict["media_id"] = media_id
elif subscribe_in.media_source is None and subscribe_in.media_id is None:
# 只有两个身份键都显式为空时才清空;全部省略则保留存量身份。
subscribe_dict["media_source"] = None
subscribe_dict["media_id"] = None
else:
return schemas.Response(
success=False,
message="更新媒体身份时必须同时提供有效的 media_source 和 media_id",
)
subscribe_dict["username"] = subscribe.username
if getattr(subscribe, "type", None) == MediaType.MUSIC.value:
# 音乐实体与曲目总数来自识别链,编辑接口不得把专辑改成单曲而提前完成订阅。
@@ -244,13 +284,18 @@ async def update_subscribe(
subscribe_dict["music_type"] = subscribe.music_type
subscribe_dict["total_tracks"] = subscribe.total_tracks \
if subscribe.music_type == MUSIC_ENTITY_ALBUM else None
if subscribe_in.total_episode and subscribe_in.total_episode > (subscribe.total_episode or 0):
total_episode_updated = "total_episode" in subscribe_in.model_fields_set
if (
total_episode_updated
and subscribe_in.total_episode
and subscribe_in.total_episode > (subscribe.total_episode or 0)
):
# 扩大目标范围时,新增加的集数尚无下载事实,应同步计入缺失集数。
subscribe_dict["lack_episode"] = (subscribe.lack_episode or 0) + (
subscribe_in.total_episode - (subscribe.total_episode or 0)
)
# 是否手动修改过总集数
if subscribe_in.total_episode != subscribe.total_episode:
if total_episode_updated and subscribe_in.total_episode != subscribe.total_episode:
subscribe_dict["manual_total_episode"] = 1
# 更新到数据库
await subscribe.async_update(db, subscribe_dict)

View File

@@ -4,7 +4,6 @@ from fastapi import APIRouter, Depends
from app import schemas
from app.chain.media import MediaChain
from app.chain.music import MusicChain
from app.chain.torrents import TorrentsChain
from app.core.config import settings
from app.core.context import MediaInfo, MusicInfo
@@ -266,7 +265,7 @@ async def reidentify_cache(
meta = (
target_context.meta_info
if isinstance(target_context.meta_info, MetaMusic)
else MusicChain.parse_query(target_context.torrent_info.title)
else MetaMusic.parse_query(target_context.torrent_info.title)
)
else:
meta = MetaInfo(