fix(v3): complete music recognition and scraping

This commit is contained in:
jxxghp
2026-08-07 23:46:54 +08:00
parent 4f22ceb1a6
commit c356d47807
11 changed files with 630 additions and 15 deletions
+44 -1
View File
@@ -1,13 +1,16 @@
from pathlib import Path
from typing import Annotated, Any, List, Optional, Union
from uuid import UUID
from fastapi import APIRouter, Depends
from app import schemas
from app.chain.media import MediaChain
from app.chain.music import MusicChain
from app.chain.tmdb import TmdbChain
from app.core.config import settings
from app.core.context import Context
from app.core.music import MusicInfo
from app.core.event import eventmanager
from app.core.meta import MetaBase
from app.core.metainfo import MetaInfo, MetaInfoPath
@@ -23,6 +26,17 @@ router = APIRouter()
MediaSource = str
def _is_valid_source_media_id(source: Optional[str], media_id: str) -> bool:
"""按媒体数据源校验原生 IDMusicBrainz 使用 UUID,其它现有来源使用数字 ID。"""
if source == "musicbrainz":
try:
UUID(media_id)
return True
except (TypeError, ValueError):
return False
return media_id.isdigit()
def _build_recognize_metainfo(
title: str,
subtitle: Optional[str] = None,
@@ -148,6 +162,12 @@ async def recognize_file(
"""
根据文件路径识别媒体信息
"""
if MusicChain.is_audio_path(path) or source == "musicbrainz":
meta_info, media_info = await MusicChain().async_recognize_by_path(
path=path,
source=source or "musicbrainz",
)
return Context(meta_info=meta_info, media_info=media_info).to_dict()
# 识别媒体信息
context = await MediaChain().async_recognize_by_path(path, source=source)
if context:
@@ -255,9 +275,32 @@ def scrape(
return schemas.Response(
success=False, message="指定媒体ID时必须同时指定媒体数据源"
)
if normalized_media_id and not normalized_media_id.isdigit():
if normalized_media_id and not _is_valid_source_media_id(media_source, normalized_media_id):
return schemas.Response(success=False, message="媒体ID格式无效")
is_music = (
type_name == MediaType.MUSIC
or media_source == "musicbrainz"
or MusicChain.is_audio_path(fileitem.path)
)
if is_music:
if type_name not in (None, MediaType.MUSIC):
return schemas.Response(success=False, message="MusicBrainz 只能用于音乐刮削")
music_info: Optional[MusicInfo] = None
if normalized_media_id:
music_info = MusicChain().recognize(
source=media_source or "musicbrainz",
media_id=normalized_media_id,
)
if not music_info:
return schemas.Response(success=False, message="刮削失败,无法识别音乐信息")
success, message = MusicChain().scrape_metadata(
fileitem=fileitem,
mediainfo=music_info,
overwrite=True,
)
return schemas.Response(success=success, message=message)
chain = MediaChain()
if normalized_media_id:
meta_info = MetaInfoPath(Path(fileitem.path))
+17 -2
View File
@@ -10,6 +10,14 @@ from app.core.security import verify_token
router = APIRouter()
CountParam = Annotated[int, Query(ge=1, le=100)]
MusicRangeParam = Annotated[
str,
Query(pattern="^(this_week|this_month|this_year|all_time)$"),
]
MusicSortParam = Annotated[
str,
Query(pattern="^listen_count\\.(desc|asc)$"),
]
def _serialize_music(info: MusicInfo) -> schemas.MusicInfo:
@@ -59,12 +67,19 @@ async def recognize_music(
async def explore_music(
page: Annotated[int, Query(ge=1)] = 1,
count: CountParam = 30,
range_name: MusicRangeParam = "this_month",
sort_by: MusicSortParam = "listen_count.desc",
min_listen_count: Annotated[int, Query(ge=0)] = 0,
with_cover: bool = False,
_: schemas.TokenPayload = Depends(verify_token),
) -> list[schemas.MusicInfo]:
"""月度全站收听榜单分页返回可搜索和订阅的音乐候选。"""
"""周期、热度和封面条件返回可搜索和订阅的音乐候选。"""
results = await MusicChain().async_chart(
range_name="this_month",
range_name=range_name,
page=page,
count=count,
sort_by=sort_by,
min_listen_count=min_listen_count,
with_cover=with_cover,
)
return [_serialize_music(info) for info in results]