mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-31 21:17:06 +08:00
213 lines
8.3 KiB
Python
213 lines
8.3 KiB
Python
from typing import Any, Optional
|
||
|
||
from sqlalchemy import Integer, String, Float, JSON, Index, or_, select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from sqlalchemy.orm import Mapped, Session, mapped_column
|
||
|
||
from app.db.base import Base, get_id_column
|
||
from app.db.models._constraints import media_identity_constraint
|
||
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaSource
|
||
|
||
|
||
class SubscribeHistory(Base):
|
||
"""
|
||
订阅历史表
|
||
"""
|
||
id = get_id_column()
|
||
# 标题
|
||
name: Mapped[str] = mapped_column(String, nullable=False, index=True)
|
||
# 年份
|
||
year: Mapped[Optional[str]] = mapped_column(String)
|
||
# 类型
|
||
type: Mapped[Optional[str]] = mapped_column(String)
|
||
# 搜索关键字
|
||
keyword: Mapped[Optional[str]] = mapped_column(String)
|
||
media_source: Mapped[Optional[str]] = mapped_column(String, index=True)
|
||
media_id: Mapped[Optional[str]] = mapped_column(String, index=True)
|
||
# 音乐实体类型:recording 单曲、album 专辑
|
||
music_type: Mapped[Optional[str]] = mapped_column(String)
|
||
# 专辑预期总曲目数
|
||
total_tracks: Mapped[Optional[int]] = mapped_column(Integer)
|
||
# 季号
|
||
season: Mapped[Optional[int]] = mapped_column(Integer)
|
||
# 海报
|
||
poster: Mapped[Optional[str]] = mapped_column(String)
|
||
# 背景图
|
||
backdrop: Mapped[Optional[str]] = mapped_column(String)
|
||
# 评分,float
|
||
vote: Mapped[Optional[float]] = mapped_column(Float)
|
||
# 简介
|
||
description: Mapped[Optional[str]] = mapped_column(String)
|
||
# 过滤规则
|
||
filter: Mapped[Optional[str]] = mapped_column(String)
|
||
# 包含
|
||
include: Mapped[Optional[str]] = mapped_column(String)
|
||
# 排除
|
||
exclude: Mapped[Optional[str]] = mapped_column(String)
|
||
# 质量
|
||
quality: Mapped[Optional[str]] = mapped_column(String)
|
||
# 分辨率
|
||
resolution: Mapped[Optional[str]] = mapped_column(String)
|
||
# 特效
|
||
effect: Mapped[Optional[str]] = mapped_column(String)
|
||
# 音乐音质等级:hires/lossless/lossy,可用正则组合
|
||
audio_quality: Mapped[Optional[str]] = mapped_column(String)
|
||
# 音频格式,可用正则组合
|
||
audio_format: Mapped[Optional[str]] = mapped_column(String)
|
||
# 最低码率(bps)
|
||
min_bitrate: Mapped[Optional[int]] = mapped_column(Integer)
|
||
# 最低位深(bit)
|
||
min_bit_depth: Mapped[Optional[int]] = mapped_column(Integer)
|
||
# 最低采样率(Hz)
|
||
min_sample_rate: Mapped[Optional[int]] = mapped_column(Integer)
|
||
# 总集数
|
||
total_episode: Mapped[Optional[int]] = mapped_column(Integer)
|
||
# 开始集数
|
||
start_episode: Mapped[Optional[int]] = mapped_column(Integer)
|
||
# 订阅完成时间
|
||
date: Mapped[Optional[str]] = mapped_column(String)
|
||
# 订阅用户
|
||
username: Mapped[Optional[str]] = mapped_column(String)
|
||
# 订阅站点
|
||
sites: Mapped[Optional[Any]] = mapped_column(JSON)
|
||
# 是否洗版
|
||
best_version: Mapped[Optional[int]] = mapped_column(Integer, default=0)
|
||
# 是否只洗全集整包,开启后电视剧洗版不按单集下载
|
||
best_version_full: Mapped[Optional[int]] = mapped_column(Integer, default=0)
|
||
# 完成时的整体优先级
|
||
current_priority: Mapped[Optional[int]] = mapped_column(Integer)
|
||
# 完成时的音乐格式
|
||
current_audio_format: Mapped[Optional[str]] = mapped_column(String)
|
||
# 完成时的音乐码率(bps)
|
||
current_bitrate: Mapped[Optional[int]] = mapped_column(Integer)
|
||
# 完成时的音乐位深(bit)
|
||
current_bit_depth: Mapped[Optional[int]] = mapped_column(Integer)
|
||
# 完成时的音乐采样率(Hz)
|
||
current_sample_rate: Mapped[Optional[int]] = mapped_column(Integer)
|
||
# 洗版时已下载剧集的优先级状态,格式:{"1": 90, "2": 100}
|
||
episode_priority: Mapped[Optional[Any]] = mapped_column(JSON)
|
||
# 保存路径
|
||
save_path: Mapped[Optional[str]] = mapped_column(String)
|
||
# 是否使用 imdbid 搜索
|
||
search_imdbid: Mapped[Optional[int]] = mapped_column(Integer, default=0)
|
||
# 自定义识别词
|
||
custom_words: Mapped[Optional[str]] = mapped_column(String)
|
||
# 自定义媒体类别
|
||
media_category: Mapped[Optional[str]] = mapped_column(String)
|
||
# 过滤规则组
|
||
filter_groups: Mapped[Optional[Any]] = mapped_column(JSON, default=list)
|
||
# 剧集组
|
||
episode_group: Mapped[Optional[str]] = mapped_column(String)
|
||
|
||
__table_args__ = (
|
||
media_identity_constraint("subscribehistory"),
|
||
Index('ix_subscribehistory_type_date', 'type', 'date'),
|
||
Index('ix_subscribehistory_media_identity', 'media_source', 'media_id'),
|
||
)
|
||
|
||
@classmethod
|
||
def list_by_type(cls, db: Session, mtype: str, page: int = 1, count: int = 30):
|
||
"""在调用方 Session 中按媒体类型分页查询订阅历史。"""
|
||
return list(db.execute(
|
||
select(cls).where(
|
||
cls.type == mtype
|
||
).order_by(
|
||
cls.date.desc()
|
||
).offset((page - 1) * count).limit(count)
|
||
).scalars().all())
|
||
|
||
@classmethod
|
||
async def async_list_by_type(cls, db: AsyncSession, mtype: str, page: int = 1, count: int = 30):
|
||
"""在调用方 AsyncSession 中按媒体类型分页查询订阅历史。"""
|
||
result = await db.execute(
|
||
select(cls).filter(
|
||
cls.type == mtype
|
||
).order_by(
|
||
cls.date.desc()
|
||
).offset((page - 1) * count).limit(count)
|
||
)
|
||
return list(result.scalars().all())
|
||
|
||
@classmethod
|
||
async def async_list_by_type_and_username(
|
||
cls,
|
||
db: AsyncSession,
|
||
mtype: str,
|
||
username: str,
|
||
page: int = 1,
|
||
count: int = 30
|
||
):
|
||
"""
|
||
按订阅 owner 查询指定类型的历史分页。
|
||
"""
|
||
if not username:
|
||
return []
|
||
result = await db.execute(
|
||
select(cls).filter(
|
||
cls.type == mtype,
|
||
cls.username == username
|
||
).order_by(
|
||
cls.date.desc()
|
||
).offset((page - 1) * count).limit(count)
|
||
)
|
||
return list(result.scalars().all())
|
||
|
||
@classmethod
|
||
def _identity_condition(
|
||
cls,
|
||
media_source: Optional[MediaSource] = None,
|
||
media_id: Optional[str] = None,
|
||
music_type: Optional[str] = None,
|
||
):
|
||
"""按统一媒体身份优先级构造订阅历史查询条件。"""
|
||
if not media_source or media_id is None or not str(media_id).strip():
|
||
return None
|
||
condition = (
|
||
(cls.media_source == str(media_source))
|
||
& (cls.media_id == str(media_id).strip())
|
||
)
|
||
if music_type == MUSIC_ENTITY_RECORDING:
|
||
return condition & or_(cls.music_type == music_type, cls.music_type.is_(None))
|
||
if music_type:
|
||
return condition & (cls.music_type == music_type)
|
||
return condition
|
||
|
||
@classmethod
|
||
def exists(
|
||
cls, db: Session, media_source: MediaSource, media_id: str,
|
||
season: Optional[int] = None,
|
||
episode_group: Optional[str] = None,
|
||
music_type: Optional[str] = None,
|
||
):
|
||
"""按媒体身份、季号及可选剧集组查询订阅历史。"""
|
||
condition = cls._identity_condition(
|
||
media_source, media_id, music_type
|
||
)
|
||
if condition is None:
|
||
return None
|
||
statement = select(cls).where(condition)
|
||
if season is not None:
|
||
statement = statement.where(cls.season == season)
|
||
statement = statement.where(cls.episode_group == episode_group)
|
||
return db.execute(statement).scalars().first()
|
||
|
||
@classmethod
|
||
async def async_exists(
|
||
cls, db: AsyncSession, media_source: MediaSource, media_id: str,
|
||
season: Optional[int] = None,
|
||
episode_group: Optional[str] = None,
|
||
music_type: Optional[str] = None,
|
||
):
|
||
"""异步按媒体身份、季号及可选剧集组查询订阅历史。"""
|
||
condition = cls._identity_condition(
|
||
media_source, media_id, music_type
|
||
)
|
||
if condition is None:
|
||
return None
|
||
query = select(cls).filter(condition)
|
||
if season is not None:
|
||
query = query.filter(cls.season == season)
|
||
query = query.filter(cls.episode_group == episode_group)
|
||
result = await db.execute(query)
|
||
return result.scalars().first()
|