mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-22 00:32:50 +08:00
feat: unify media recognition and music lifecycle
This commit is contained in:
@@ -14,7 +14,13 @@ from app.db.site_oper import SiteOper
|
||||
from app.db.systemconfig_oper import SystemConfigOper
|
||||
from app.db.user_oper import get_current_active_user
|
||||
from app.helper.directory import DirectoryHelper
|
||||
from app.schemas.types import SystemConfigKey
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
MediaType,
|
||||
MusicTargetEntityType,
|
||||
SystemConfigKey,
|
||||
)
|
||||
from app.utils.media import is_music_media_source, normalize_music_type
|
||||
from app.utils.security import SecurityUtils
|
||||
|
||||
router = APIRouter()
|
||||
@@ -114,6 +120,7 @@ def add(
|
||||
anilistid: Annotated[int | None, Body()] = None,
|
||||
media_source: Annotated[MediaSource | None, Body()] = None,
|
||||
media_id: Annotated[str | None, Body()] = None,
|
||||
music_type: Annotated[MusicTargetEntityType | None, Body()] = None,
|
||||
downloader: Annotated[str | None, Body()] = None,
|
||||
# 保存路径, 支持<storage>:<path>, 如rclone:/MP, smb:/server/share/Movies等
|
||||
save_path: Annotated[str | None, Body()] = None,
|
||||
@@ -122,8 +129,30 @@ def add(
|
||||
"""
|
||||
添加下载任务(不含媒体信息)
|
||||
"""
|
||||
normalized_music_type = normalize_music_type(music_type, allow_artist=False)
|
||||
if music_type is not None and not normalized_music_type:
|
||||
return schemas.Response(
|
||||
success=False,
|
||||
message="音乐实体类型无效,仅支持 recording 或 album",
|
||||
)
|
||||
is_music = (
|
||||
torrent_in.category in (MediaType.MUSIC, MediaType.MUSIC.value, "music")
|
||||
or is_music_media_source(media_source)
|
||||
or normalized_music_type is not None
|
||||
)
|
||||
if is_music and media_source and not is_music_media_source(media_source):
|
||||
return schemas.Response(
|
||||
success=False,
|
||||
message="音乐下载只能使用音乐元数据源",
|
||||
)
|
||||
if is_music and not normalized_music_type:
|
||||
normalized_music_type = MUSIC_ENTITY_RECORDING
|
||||
# 元数据
|
||||
metainfo = MetaInfo(title=torrent_in.title, subtitle=torrent_in.description)
|
||||
metainfo = (
|
||||
MusicChain.parse_query(torrent_in.title)
|
||||
if is_music
|
||||
else MetaInfo(title=torrent_in.title, subtitle=torrent_in.description)
|
||||
)
|
||||
# 媒体信息
|
||||
if tmdbid or doubanid or bangumiid or anilistid or media_id:
|
||||
mediainfo = MediaChain().recognize_media(
|
||||
@@ -134,12 +163,16 @@ def add(
|
||||
doubanid=doubanid,
|
||||
bangumiid=bangumiid,
|
||||
anilistid=anilistid,
|
||||
mtype=MediaType.MUSIC if is_music else None,
|
||||
music_type=normalized_music_type,
|
||||
)
|
||||
else:
|
||||
mediainfo = MediaChain().recognize_by_meta(
|
||||
metainfo,
|
||||
source=media_source,
|
||||
obtain_images=False,
|
||||
mtype=MediaType.MUSIC if is_music else None,
|
||||
music_type=normalized_music_type,
|
||||
)
|
||||
if not mediainfo:
|
||||
return schemas.Response(success=False, message="无法识别媒体信息")
|
||||
|
||||
@@ -18,10 +18,11 @@ from app.db.models import User
|
||||
from app.db.user_oper import get_current_active_user, get_current_active_superuser
|
||||
from app.schemas import MediaType, MediaRecognizeConvertEventData
|
||||
from app.schemas.category import CategoryConfig
|
||||
from app.schemas.types import ChainEventType
|
||||
from app.schemas.types import ChainEventType, MUSIC_ENTITY_RECORDING
|
||||
from app.utils.media import (
|
||||
MEDIA_SOURCE_ID_FIELDS,
|
||||
is_music_media_source,
|
||||
normalize_music_type,
|
||||
parse_media_key,
|
||||
)
|
||||
|
||||
@@ -30,13 +31,16 @@ MediaSource = str
|
||||
|
||||
|
||||
def _is_valid_source_media_id(source: Optional[str], media_id: str) -> bool:
|
||||
"""按媒体数据源校验原生 ID,MusicBrainz 使用 UUID,其它内置来源使用数字 ID。"""
|
||||
"""按媒体数据源校验原生 ID,并兼容豆瓣音乐的曲目复合 ID。"""
|
||||
if source == "musicbrainz":
|
||||
try:
|
||||
UUID(media_id)
|
||||
return True
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
if source == "doubanmusic" and ":" in media_id:
|
||||
album_id, track_number = media_id.split(":", 1)
|
||||
return album_id.isdigit() and track_number.isdigit()
|
||||
return media_id.isdigit()
|
||||
|
||||
|
||||
@@ -267,6 +271,7 @@ def scrape(
|
||||
media_source: Optional[MediaSource] = None,
|
||||
media_id: Optional[str] = None,
|
||||
type_name: Optional[MediaType] = None,
|
||||
music_type: Optional[str] = None,
|
||||
_: schemas.TokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""
|
||||
@@ -277,6 +282,7 @@ def scrape(
|
||||
:param media_source: 请求级媒体数据源
|
||||
:param media_id: 数据源原生ID
|
||||
:param type_name: 媒体类型
|
||||
:param music_type: 音乐实体类型,支持 recording 和 album
|
||||
:param _: Token校验
|
||||
"""
|
||||
if not fileitem or not fileitem.path:
|
||||
@@ -299,11 +305,21 @@ def scrape(
|
||||
return schemas.Response(success=False, message="音乐元数据源只能用于音乐刮削")
|
||||
music_info: Optional[MusicInfo] = None
|
||||
if normalized_media_id:
|
||||
normalized_music_type = normalize_music_type(
|
||||
music_type or MUSIC_ENTITY_RECORDING,
|
||||
allow_artist=False,
|
||||
)
|
||||
if not normalized_music_type:
|
||||
return schemas.Response(
|
||||
success=False,
|
||||
message="音乐实体类型无效,仅支持 recording 或 album",
|
||||
)
|
||||
# 音乐与影视共用统一识别入口,按媒体源和原生 ID 恢复音乐详情
|
||||
music_info = MediaChain().recognize_media(
|
||||
source=media_source or "musicbrainz",
|
||||
mediaid=normalized_media_id,
|
||||
mtype=MediaType.MUSIC,
|
||||
music_type=normalized_music_type,
|
||||
)
|
||||
if not music_info:
|
||||
return schemas.Response(success=False, message="刮削失败,无法识别音乐信息")
|
||||
|
||||
@@ -63,10 +63,15 @@ async def recognize_music(
|
||||
_: schemas.TokenPayload = Depends(verify_token),
|
||||
) -> schemas.MusicInfo:
|
||||
"""根据音乐元数据来源和媒体 ID 获取标准详情,与影视识别共用统一入口。"""
|
||||
recognize_kwargs = {
|
||||
"source": request.source,
|
||||
"mediaid": request.media_id,
|
||||
"mtype": MediaType.MUSIC,
|
||||
}
|
||||
if request.music_type is not None:
|
||||
recognize_kwargs["music_type"] = request.music_type
|
||||
info = await MediaChain().async_recognize_media(
|
||||
source=request.source,
|
||||
mediaid=request.media_id,
|
||||
mtype=MediaType.MUSIC,
|
||||
**recognize_kwargs,
|
||||
)
|
||||
if not info:
|
||||
raise HTTPException(status_code=404, detail="未识别到音乐信息")
|
||||
|
||||
@@ -18,7 +18,7 @@ from app.helper.locale import LocaleHelper
|
||||
from app.log import logger
|
||||
from app.schemas import MediaRecognizeConvertEventData
|
||||
from app.schemas.types import MediaType, ChainEventType
|
||||
from app.utils.media import parse_media_key, resolve_media_identity
|
||||
from app.utils.media import normalize_music_type, parse_media_key, resolve_media_identity
|
||||
from app.utils.security import SecurityUtils
|
||||
|
||||
router = APIRouter()
|
||||
@@ -63,14 +63,30 @@ async def _resolve_media_search_params(
|
||||
title: Optional[str] = None,
|
||||
year: Optional[str] = None,
|
||||
media_season: Optional[int] = None,
|
||||
music_type: Optional[str] = None,
|
||||
) -> tuple[Optional[dict], str]:
|
||||
"""将任意来源媒体键解析为 SearchChain 可直接使用的识别参数。"""
|
||||
normalized_music_type = None
|
||||
if music_type:
|
||||
normalized_music_type = normalize_music_type(music_type, allow_artist=False)
|
||||
if not normalized_music_type:
|
||||
return None, "音乐实体类型无效,仅支持 recording 或 album"
|
||||
if media_type != MediaType.MUSIC:
|
||||
return None, "music_type 仅能用于音乐资源搜索"
|
||||
|
||||
def build_params(source: str, source_media_id: str) -> dict:
|
||||
"""构造带可选音乐实体命名空间的精确搜索参数。"""
|
||||
params = {"source": source, "mediaid": source_media_id}
|
||||
if normalized_music_type:
|
||||
params["music_type"] = normalized_music_type
|
||||
return params
|
||||
|
||||
source, source_media_id = parse_media_key(mediaid)
|
||||
if source and source_media_id:
|
||||
if source in {"themoviedb", "bangumi", "anilist"} \
|
||||
and not source_media_id.isdigit():
|
||||
return None, "媒体ID格式错误"
|
||||
return {"source": source, "mediaid": source_media_id}, ""
|
||||
return build_params(source, source_media_id), ""
|
||||
|
||||
event_data = MediaRecognizeConvertEventData(
|
||||
mediaid=mediaid, convert_type=settings.RECOGNIZE_SOURCE
|
||||
@@ -82,10 +98,7 @@ async def _resolve_media_search_params(
|
||||
event_data = event.event_data
|
||||
search_id = event_data.media_dict.get("id")
|
||||
if search_id is not None:
|
||||
return {
|
||||
"source": event_data.convert_type,
|
||||
"mediaid": str(search_id),
|
||||
}, ""
|
||||
return build_params(event_data.convert_type, str(search_id)), ""
|
||||
|
||||
if not title:
|
||||
return None, "未知的媒体ID"
|
||||
@@ -98,16 +111,16 @@ async def _resolve_media_search_params(
|
||||
if media_season is not None:
|
||||
meta.type = MediaType.TV
|
||||
meta.begin_season = media_season
|
||||
mediainfo = await MediaChain().async_recognize_by_meta(
|
||||
meta,
|
||||
obtain_images=False,
|
||||
)
|
||||
recognize_kwargs = {"obtain_images": False}
|
||||
if normalized_music_type:
|
||||
recognize_kwargs["music_type"] = normalized_music_type
|
||||
mediainfo = await MediaChain().async_recognize_by_meta(meta, **recognize_kwargs)
|
||||
if not mediainfo:
|
||||
return None, "未识别到媒体信息"
|
||||
source, source_media_id = resolve_media_identity(media=mediainfo)
|
||||
if not source or not source_media_id:
|
||||
return None, "媒体信息缺少有效ID"
|
||||
return {"source": source, "mediaid": source_media_id}, ""
|
||||
return build_params(source, source_media_id), ""
|
||||
|
||||
|
||||
def _sse_event(data: dict, locale: Optional[str] = None) -> str:
|
||||
@@ -390,6 +403,7 @@ async def search_by_id_stream(
|
||||
year: Optional[str] = None,
|
||||
season: Optional[str] = None,
|
||||
sites: Optional[str] = None,
|
||||
music_type: Optional[str] = None,
|
||||
_: schemas.TokenPayload = Depends(verify_resource_token),
|
||||
) -> Any:
|
||||
"""
|
||||
@@ -409,6 +423,7 @@ async def search_by_id_stream(
|
||||
title=title,
|
||||
year=year,
|
||||
media_season=media_season,
|
||||
music_type=music_type,
|
||||
)
|
||||
if not search_params:
|
||||
yield {"type": "error", "success": False, "message": message}
|
||||
@@ -440,6 +455,7 @@ async def search_by_id(
|
||||
year: Optional[str] = None,
|
||||
season: Optional[str] = None,
|
||||
sites: Optional[str] = None,
|
||||
music_type: Optional[str] = None,
|
||||
_: schemas.TokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""
|
||||
@@ -453,6 +469,7 @@ async def search_by_id(
|
||||
title=title,
|
||||
year=year,
|
||||
media_season=media_season,
|
||||
music_type=music_type,
|
||||
)
|
||||
if not search_params:
|
||||
return schemas.Response(success=False, message=message)
|
||||
|
||||
@@ -6,9 +6,10 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app import schemas
|
||||
from app.chain.music import MusicChain
|
||||
from app.chain.subscribe import SubscribeChain
|
||||
from app.core.config import settings
|
||||
from app.core.context import MUSIC_ENTITY_ALBUM, MUSIC_ENTITY_RECORDING, MediaInfo
|
||||
from app.core.context import MediaInfo
|
||||
from app.core.event import eventmanager
|
||||
from app.core.metainfo import MetaInfo
|
||||
from app.core.security import verify_token, verify_apitoken
|
||||
@@ -22,7 +23,13 @@ from app.helper.server import MoviePilotServerHelper
|
||||
from app.log import logger
|
||||
from app.scheduler import Scheduler
|
||||
from app.schemas.event import SubscribeModifiedEventData
|
||||
from app.schemas.types import MediaType, EventType, SystemConfigKey
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
MediaType,
|
||||
EventType,
|
||||
SystemConfigKey,
|
||||
)
|
||||
from app.utils.media import normalize_media_source, parse_media_key
|
||||
|
||||
router = APIRouter()
|
||||
@@ -117,6 +124,14 @@ def matches_subscribe_music_type(
|
||||
or (music_type == MUSIC_ENTITY_RECORDING and subscribe_music_type is None)
|
||||
|
||||
|
||||
def music_subscribe_title_candidates(title: str) -> List[str]:
|
||||
"""生成音乐订阅标题兜底候选,保留精确标题并追加音乐语义解析结果。"""
|
||||
parsed_title = MusicChain.parse_query(title).title
|
||||
return list(dict.fromkeys(
|
||||
candidate for candidate in (title, parsed_title) if candidate
|
||||
))
|
||||
|
||||
|
||||
async def list_subscribes_by_media_key(
|
||||
db: AsyncSession,
|
||||
media_key: str,
|
||||
@@ -326,18 +341,27 @@ async def subscribe_mediaid(
|
||||
title_check = not result and bool(title) and source != "themoviedb"
|
||||
# 使用名称检查订阅
|
||||
if title_check and title:
|
||||
meta = MetaInfo(title)
|
||||
if season is not None:
|
||||
meta.begin_season = season
|
||||
subscribes = await Subscribe.async_list_by_title(
|
||||
db, title=meta.name, season=meta.begin_season
|
||||
)
|
||||
title_season = None
|
||||
if music_type:
|
||||
subscribes = [
|
||||
subscribe for subscribe in subscribes
|
||||
if matches_subscribe_music_type(subscribe, music_type)
|
||||
]
|
||||
result = select_accessible_subscribe(subscribes, current_user)
|
||||
title_candidates = music_subscribe_title_candidates(title)
|
||||
else:
|
||||
title_meta = MetaInfo(title)
|
||||
if season is not None:
|
||||
title_meta.begin_season = season
|
||||
title_season = title_meta.begin_season
|
||||
title_candidates = [title_meta.name]
|
||||
for candidate_title in title_candidates:
|
||||
subscribes = await Subscribe.async_list_by_title(
|
||||
db, title=candidate_title, season=title_season
|
||||
)
|
||||
if music_type:
|
||||
subscribes = [
|
||||
subscribe for subscribe in subscribes
|
||||
if matches_subscribe_music_type(subscribe, music_type)
|
||||
]
|
||||
result = select_accessible_subscribe(subscribes, current_user)
|
||||
if result:
|
||||
break
|
||||
|
||||
return result if result else Subscribe()
|
||||
|
||||
|
||||
@@ -4,16 +4,28 @@ 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
|
||||
from app.core.context import MediaInfo, MusicInfo
|
||||
from app.core.meta import MetaMusic
|
||||
from app.core.metainfo import MetaInfo
|
||||
from app.db.models import User
|
||||
from app.db.user_oper import (
|
||||
get_current_active_superuser,
|
||||
get_current_active_superuser_async,
|
||||
)
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
MediaType,
|
||||
MusicTargetEntityType,
|
||||
)
|
||||
from app.utils.crypto import HashUtils
|
||||
from app.utils.media import (
|
||||
is_music_media_source,
|
||||
normalize_music_type,
|
||||
resolve_media_identity,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -41,6 +53,7 @@ async def torrents_cache(_: User = Depends(get_current_active_superuser_async)):
|
||||
torrent_hash = HashUtils.md5(
|
||||
f"{context.torrent_info.title}{context.torrent_info.description}"
|
||||
)
|
||||
media_source, media_id = resolve_media_identity(media=context.media_info)
|
||||
torrent_data.append(
|
||||
{
|
||||
"hash": torrent_hash,
|
||||
@@ -55,6 +68,9 @@ async def torrents_cache(_: User = Depends(get_current_active_superuser_async)):
|
||||
else "",
|
||||
"media_year": context.media_info.year if context.media_info else "",
|
||||
"media_type": context.media_info.type if context.media_info else "",
|
||||
"media_source": media_source,
|
||||
"media_id": media_id,
|
||||
"music_type": getattr(context.media_info, "music_type", None),
|
||||
"season_episode": context.meta_info.season_episode
|
||||
if context.meta_info
|
||||
else "",
|
||||
@@ -181,6 +197,7 @@ async def reidentify_cache(
|
||||
anilistid: Optional[int] = None,
|
||||
media_source: Optional[str] = None,
|
||||
media_id: Optional[str] = None,
|
||||
music_type: Optional[MusicTargetEntityType] = None,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
):
|
||||
"""
|
||||
@@ -193,6 +210,7 @@ async def reidentify_cache(
|
||||
:param anilistid: 手动指定的 AniList ID
|
||||
:param media_source: 媒体数据源
|
||||
:param media_id: 数据源原生 ID
|
||||
:param music_type: 音乐实体类型,仅支持单曲或专辑
|
||||
:param _: 当前用户,必须是超级用户
|
||||
"""
|
||||
|
||||
@@ -221,13 +239,53 @@ async def reidentify_cache(
|
||||
if not target_context:
|
||||
return schemas.Response(success=False, message="未找到指定的种子")
|
||||
|
||||
# 重新识别
|
||||
meta = MetaInfo(
|
||||
title=target_context.torrent_info.title,
|
||||
subtitle=target_context.torrent_info.description,
|
||||
existing_music_type = normalize_music_type(
|
||||
getattr(target_context.media_info, "music_type", None),
|
||||
allow_artist=False,
|
||||
)
|
||||
if tmdbid or doubanid or bangumiid or anilistid or media_source or media_id:
|
||||
# 手动指定媒体信息
|
||||
normalized_music_type = normalize_music_type(
|
||||
music_type,
|
||||
allow_artist=False,
|
||||
)
|
||||
if music_type is not None and not normalized_music_type:
|
||||
return schemas.Response(
|
||||
success=False,
|
||||
message="音乐实体类型无效,仅支持 recording 或 album",
|
||||
)
|
||||
is_music = (
|
||||
getattr(target_context.media_info, "type", None) == MediaType.MUSIC
|
||||
or isinstance(target_context.meta_info, MetaMusic)
|
||||
or target_context.torrent_info.category
|
||||
in (MediaType.MUSIC, MediaType.MUSIC.value, "music")
|
||||
or is_music_media_source(media_source)
|
||||
or normalized_music_type is not None
|
||||
)
|
||||
if is_music and media_source and not is_music_media_source(media_source):
|
||||
return schemas.Response(
|
||||
success=False,
|
||||
message="音乐重新识别只能使用音乐元数据源",
|
||||
)
|
||||
if is_music and not normalized_music_type:
|
||||
normalized_music_type = existing_music_type or MUSIC_ENTITY_RECORDING
|
||||
|
||||
# 重识别沿用原媒体域;音乐标题必须使用 MetaMusic,避免误入影视模块。
|
||||
if is_music:
|
||||
meta = (
|
||||
target_context.meta_info
|
||||
if isinstance(target_context.meta_info, MetaMusic)
|
||||
else MusicChain.parse_query(target_context.torrent_info.title)
|
||||
)
|
||||
else:
|
||||
meta = MetaInfo(
|
||||
title=target_context.torrent_info.title,
|
||||
subtitle=target_context.torrent_info.description,
|
||||
)
|
||||
|
||||
has_explicit_id = bool(
|
||||
tmdbid or doubanid or bangumiid or anilistid or media_id
|
||||
)
|
||||
if has_explicit_id:
|
||||
# 手动指定媒体身份时执行精确识别。
|
||||
mediainfo = await media_chain.async_recognize_media(
|
||||
meta=meta,
|
||||
tmdbid=tmdbid,
|
||||
@@ -236,14 +294,27 @@ async def reidentify_cache(
|
||||
anilistid=anilistid,
|
||||
source=media_source,
|
||||
mediaid=media_id,
|
||||
mtype=MediaType.MUSIC if is_music else None,
|
||||
music_type=normalized_music_type,
|
||||
)
|
||||
else:
|
||||
# 自动重新识别
|
||||
mediainfo = await media_chain.async_recognize_by_meta(meta)
|
||||
# 未指定 ID 时按标题识别,请求级来源仍用于约束本次识别。
|
||||
mediainfo = await media_chain.async_recognize_by_meta(
|
||||
meta,
|
||||
source=media_source,
|
||||
mtype=MediaType.MUSIC if is_music else None,
|
||||
music_type=normalized_music_type,
|
||||
)
|
||||
|
||||
if not mediainfo:
|
||||
# 创建空的媒体信息
|
||||
mediainfo = MediaInfo()
|
||||
# 失败占位仍保留原媒体域,避免音乐缓存被误写进影视缓存文件。
|
||||
mediainfo = (
|
||||
MusicInfo(
|
||||
music_type=normalized_music_type or MUSIC_ENTITY_RECORDING
|
||||
)
|
||||
if is_music
|
||||
else MediaInfo()
|
||||
)
|
||||
else:
|
||||
# 清理多余数据
|
||||
mediainfo.clear()
|
||||
@@ -266,6 +337,9 @@ async def reidentify_cache(
|
||||
"media_type": mediainfo.type.value
|
||||
if mediainfo and mediainfo.type
|
||||
else "",
|
||||
"media_source": getattr(mediainfo, "source", None),
|
||||
"media_id": getattr(mediainfo, "media_id", None),
|
||||
"music_type": getattr(mediainfo, "music_type", None),
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
|
||||
@@ -53,6 +53,7 @@ def query_name(
|
||||
media_path = DirectoryHelper.get_media_root_path(
|
||||
rename_format=settings.RENAME_FORMAT(context.media_info.type),
|
||||
rename_path=Path(new_path),
|
||||
media_type=context.media_info.type,
|
||||
)
|
||||
if media_path:
|
||||
new_name = media_path.name
|
||||
@@ -338,6 +339,9 @@ def manual_transfer(
|
||||
transer_item.media_id = (
|
||||
history.media_id or transer_item.media_id
|
||||
)
|
||||
transer_item.music_type = (
|
||||
getattr(history, "music_type", None) or transer_item.music_type
|
||||
)
|
||||
transer_item.season = (
|
||||
int(str(history.seasons).replace("S", ""))
|
||||
if history.seasons
|
||||
@@ -459,6 +463,7 @@ def manual_transfer(
|
||||
anilistid=transer_item.anilistid,
|
||||
media_source=transer_item.media_source,
|
||||
media_id=transer_item.media_id,
|
||||
music_type=transer_item.music_type,
|
||||
mtype=mtype,
|
||||
season=transer_item.season,
|
||||
episode_group=transer_item.episode_group,
|
||||
@@ -546,6 +551,7 @@ def manual_transfer(
|
||||
anilistid=transer_item.anilistid,
|
||||
media_source=transer_item.media_source,
|
||||
media_id=transer_item.media_id,
|
||||
music_type=transer_item.music_type,
|
||||
mtype=mtype,
|
||||
season=transer_item.season,
|
||||
episode_group=transer_item.episode_group,
|
||||
|
||||
Reference in New Issue
Block a user