Files
MoviePilot/app/db/models/subscribehistory.py
T

213 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()