feat(download): persist poster and backdrop images (#6232)

This commit is contained in:
jxxghp
2026-08-03 21:38:26 +08:00
committed by GitHub
parent 36dd381260
commit a700ba9379
9 changed files with 128 additions and 8 deletions
+4 -1
View File
@@ -951,6 +951,7 @@ class DownloadChain(ChainBase):
seasons=_meta.season,
episodes=download_episodes or _meta.episode,
image=_media.get_backdrop_image(),
poster=_media.get_poster_image(),
downloader=_downloader,
download_hash=_hash,
torrent_name=_torrent.title,
@@ -1777,7 +1778,9 @@ class DownloadChain(ChainBase):
"title": history.title,
"season": history.seasons,
"episode": history.episodes,
"image": history.image,
"image": history.poster,
"poster": history.poster,
"backdrop": history.image,
}
torrent.site_name = history.torrent_site
# 下载用户
+3 -1
View File
@@ -39,8 +39,10 @@ class DownloadHistory(Base):
seasons = Column(String)
# Exx
episodes = Column(String)
# 海报
# 背景图
image = Column(String)
# 海报
poster = Column(String)
# 下载器
downloader = Column(String)
# 下载任务Hash
+7 -1
View File
@@ -4,6 +4,10 @@ from pydantic import BaseModel, ConfigDict, Field
class DownloadHistory(BaseModel):
"""
下载历史记录
"""
# ID
id: int
# 保存路程
@@ -34,8 +38,10 @@ class DownloadHistory(BaseModel):
seasons: Optional[str] = None
# 集Exx
episodes: Optional[str] = None
# 海报
# 背景图
image: Optional[str] = None
# 海报
poster: Optional[str] = None
# 下载器Hash
download_hash: Optional[str] = None
# 种子名称
+42
View File
@@ -0,0 +1,42 @@
"""2.2.15
为下载历史增加海报字段
Revision ID: a8c4e2f6b1d9
Revises: f7b2d5c9a301
Create Date: 2026-08-03
"""
from alembic import op
import sqlalchemy as sa
revision = "a8c4e2f6b1d9"
down_revision = "f7b2d5c9a301"
branch_labels = None
depends_on = None
def _has_column(table_name: str, column_name: str) -> bool:
"""检查数据表是否已存在指定字段。"""
inspector = sa.inspect(op.get_bind())
if table_name not in inspector.get_table_names():
return False
return any(
column["name"] == column_name
for column in inspector.get_columns(table_name)
)
def upgrade() -> None:
"""为下载历史增加可空海报字段。"""
if not _has_column("downloadhistory", "poster"):
op.add_column(
"downloadhistory",
sa.Column("poster", sa.String(), nullable=True),
)
def downgrade() -> None:
"""移除下载历史海报字段。"""
if _has_column("downloadhistory", "poster"):
op.drop_column("downloadhistory", "poster")
+8 -1
View File
@@ -170,7 +170,7 @@ AniList 榜单、探索、详情、人物和推荐接口优先通过 `anilist-ch
| 方法 | 路径 | 说明 |
| :--- | :--- | :--- |
| GET | `/api/v1/download/` | 查询正在下载的任务,参数:`name`;关联下载历史时返回媒体类型来源站点 `site_name` |
| GET | `/api/v1/download/` | 查询正在下载的任务,参数:`name`;关联下载历史时返回媒体类型来源站点 `site_name`,以及 `media.poster` 海报和 `media.backdrop` 背景图;兼容字段 `media.image``media.poster` 相同 |
| POST | `/api/v1/download/` | 添加含媒体信息的下载任务,请求体包含媒体信息和种子信息 |
| POST | `/api/v1/download/add` | 添加不含媒体信息的下载任务,请求体包含 `torrent_in`,可选 `media_source` + `media_id`;继续兼容四种专用 ID,并支持 `downloader``save_path` |
| POST | `/api/v1/download/subtitle` | 下载字幕到识别出的媒体下载目录,请求体包含 `subtitle_in`,可选 `media_source` + `media_id`;继续兼容四种专用 ID,并支持 `save_path` |
@@ -180,6 +180,13 @@ AniList 榜单、探索、详情、人物和推荐接口优先通过 `anilist-ch
| GET | `/api/v1/download/paths` | 查询可用于下载接口 `save_path` 参数的下载路径 |
| DELETE | `/api/v1/download/{hashString}` | 删除下载任务,参数:`name` |
#### 历史
| 方法 | 路径 | 说明 |
| :--- | :--- | :--- |
| GET | `/api/v1/history/download` | 按下载时间倒序查询下载历史,参数:`page``count``poster` 为海报,兼容字段 `image` 为背景图 |
| DELETE | `/api/v1/history/download` | 删除下载历史,请求体为下载历史记录 |
#### 系统
| 方法 | 路径 | 说明 |
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: moviepilot-api
version: 6
version: 7
description: >-
Use this skill when you need to call MoviePilot REST API endpoints directly
with the bundled Python client. Covers MoviePilot HTTP endpoints across media
@@ -251,7 +251,7 @@ Streaming search sends `{"type":"heartbeat"}` every 15 seconds without business
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/history/download` | Download history, newest first. Params: `page`, `count` |
| GET | `/api/v1/history/download` | Download history, newest first. Params: `page`, `count`. `poster` is the poster image; legacy `image` is the backdrop image. |
| DELETE | `/api/v1/history/download` | Delete download history. Body: DownloadHistory JSON |
| GET | `/api/v1/history/transfer` | Transfer history. Params: `title`, `page`, `count`, `status` |
| DELETE | `/api/v1/history/transfer` | Delete transfer history. Params: `deletesrc`, `deletedest`. Body: TransferHistory |
+1 -1
View File
@@ -23,7 +23,7 @@ def test_modified_builtin_skills_have_incremented_versions() -> None:
"""本次修改过的内置技能必须递增版本,确保用户端同步更新。"""
expected_versions = {
"database-operation": "3",
"moviepilot-api": "6",
"moviepilot-api": "7",
"moviepilot-cli": "6",
"moviepilot-update": "3",
"transfer-failed-retry": "2",
+11 -1
View File
@@ -249,9 +249,11 @@ def test_download_single_persists_custom_words_snapshot(monkeypatch):
"""捕获写入下载历史的字段,验证识别词快照确实落库。"""
def add(self, **kwargs):
"""捕获下载历史字段。"""
captured.update(kwargs)
def add_files(self, _files):
"""忽略与当前断言无关的下载文件记录。"""
pass
_FakeThreadHelper.submitted = []
@@ -278,6 +280,8 @@ def test_download_single_persists_custom_words_snapshot(monkeypatch):
year="2024",
tmdb_id=1,
genre_ids=[18],
poster_path="https://images.example.com/original/poster.jpg",
backdrop_path="https://images.example.com/original/backdrop.jpg",
),
torrent_info=TorrentInfo(
title="Demo Show 2024",
@@ -298,6 +302,8 @@ def test_download_single_persists_custom_words_snapshot(monkeypatch):
assert result == "hash123"
assert captured["custom_words"] == custom_words
assert captured["poster"] == "https://images.example.com/w500/poster.jpg"
assert captured["image"] == "https://images.example.com/w500/backdrop.jpg"
def test_save_subtitle_response_creates_missing_temp_directory(monkeypatch, tmp_path):
@@ -1045,7 +1051,8 @@ def test_downloading_includes_media_type_and_source_site(monkeypatch):
torrent = DownloaderTorrent(hash="download-hash", title="Demo.Release")
history = SimpleNamespace(
episodes="E02",
image="https://images.example.com/demo.jpg",
image="https://images.example.com/backdrop.jpg",
poster="https://images.example.com/poster.jpg",
seasons="S01",
title="示例剧集",
tmdbid=1001,
@@ -1066,6 +1073,9 @@ def test_downloading_includes_media_type_and_source_site(monkeypatch):
assert result == [torrent]
assert torrent.media["type"] == "电视剧"
assert torrent.media["image"] == "https://images.example.com/poster.jpg"
assert torrent.media["poster"] == "https://images.example.com/poster.jpg"
assert torrent.media["backdrop"] == "https://images.example.com/backdrop.jpg"
assert torrent.site_name == "示例站点"
assert torrent.userid == "user-1"
assert torrent.username == "tester"
@@ -0,0 +1,50 @@
import importlib
import sqlalchemy as sa
from alembic.migration import MigrationContext
from alembic.operations import Operations
def test_downloadhistory_poster_migration_adds_and_removes_column(monkeypatch) -> None:
"""迁移应为下载历史增加可空海报字段,并支持回滚。"""
migration = importlib.import_module(
"database.versions.a8c4e2f6b1d9_2_2_15"
)
engine = sa.create_engine("sqlite://")
metadata = sa.MetaData()
download_history = sa.Table(
"downloadhistory",
metadata,
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("image", sa.String()),
)
with engine.begin() as connection:
metadata.create_all(connection)
connection.execute(download_history.insert(), {
"id": 1,
"image": "https://images.example.com/backdrop.jpg",
})
context = MigrationContext.configure(connection)
monkeypatch.setattr(migration, "op", Operations(context))
migration.upgrade()
migration.upgrade()
migrated = sa.Table(
"downloadhistory",
sa.MetaData(),
autoload_with=connection,
)
row = connection.execute(sa.select(migrated)).mappings().one()
assert "poster" in migrated.c
assert row["image"] == "https://images.example.com/backdrop.jpg"
assert row["poster"] is None
migration.downgrade()
rolled_back = sa.Table(
"downloadhistory",
sa.MetaData(),
autoload_with=connection,
)
assert "poster" not in rolled_back.c