Skip to content

fix(media): 实现媒体反代并修复下载崩溃,新增 GIF 大小限制压缩#77

Merged
FlanChanXwO merged 2 commits into
devfrom
fix/media-relay-and-gif-compress
Jun 1, 2026
Merged

fix(media): 实现媒体反代并修复下载崩溃,新增 GIF 大小限制压缩#77
FlanChanXwO merged 2 commits into
devfrom
fix/media-relay-and-gif-compress

Conversation

@FlanChanXwO
Copy link
Copy Markdown
Owner

@FlanChanXwO FlanChanXwO commented Jun 1, 2026

修复一个严重回归:所有媒体(图片/视频/音频)都发不出去。并补上 GIF 大小限制压缩。

prepare_mediaMediaDownloader.get_or_download_prepared() got an unexpected keyword argument 'image_relay_base_url'。根因是 #74base_sender 加了反代管线,把 image_relay_base_url/media_relay_base_url 通过 **relay_kwargs 传给下载器,但 MediaDownloader.get_or_download(_prepared) 从未接收这两个参数 → 每次下载抛 TypeError 被 except 吞掉、download_failed=True,媒体静默丢失。反代功能虽有 config + 文档,但下载器侧实现从未写过。

Modifications / 改动点

  • src/infrastructure/media/media_downloader.py:给 get_or_downloadget_or_download_prepared 增加 image_relay_base_url / media_relay_base_url 参数并真正接通。

    • 新增 _build_relay_url(支持 https://wsrv.nl/https://wsrv.nl/?url= 两种形式)和 _select_relay_base(图片优先图片反代,非图片走通用媒体反代,都未配置则不反代)。
    • 抓取处做「先反代再回源」:反代下载失败回退原始 URL。缓存 key 与 original_url 始终保持原始 URL;m3u8 路径不走反代;两个反代为空时行为与之前逐字节一致。
  • src/infrastructure/utils/ffmpeg_helper.py:新增 FFmpegTool.transcode_to_gif_under_limit,逐步降分辨率/帧率把视频转为不超过指定字节数的 GIF;由 media_downloader 在生成 GIF 变体时调用。

  • 测试:新增反代回归测试(各种 base 形式、图片/非图片选择、先反代再回源、both-empty、prepared 透传)与 GIF 压缩测试;修正 test_base_sender_ffmpeg 中自 fix(db,onebot): drop legacy link_preview column and add napcat stream upload #74 起就过时的预期 call dict(补 relay 键)。

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

$ pytest tests/unit/infrastructure/test_media_downloader.py tests/unit/infrastructure/test_base_sender_ffmpeg.py -q
35 passed

$ uv run ruff check  <4 files>   # All checks passed!
$ uv run ruff format --check <4 files>   # 4 files already formatted

新增的反代测试断言 download_to_temp 实际拿到的是反代 URL、而缓存 key / original_url 仍为原始 URL;并覆盖反代失败回源。

Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed them with the authors through issues/emails, etc. / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above. / 我的更改经过了良好的测试,并已在上方提供了"验证步骤"和"运行截图"
  • 🤓 I have ensured that no new dependencies are introduced. / 我确保没有引入新依赖库。
  • 😮 My changes do not introduce malicious code. / 我的更改没有引入恶意代码。

Summary by Sourcery

通过将反向代理支持接入媒体下载器来修复媒体下载的回归问题,并为生成的 GIF 变体添加带大小限制的 GIF 压缩功能。

Bug 修复:

  • 通过在媒体下载器中正确处理图像/媒体中继参数,并在中继获取失败时回退到源站,恢复所有媒体类型的发送。

增强功能:

  • 添加反向代理 URL 构建和选择逻辑,使图像和非图像媒体可以选择性地通过可配置的中继基础地址获取,同时保留原始 URL 以便缓存。
  • 引入 GIF 转码辅助工具,通过逐步降低分辨率和帧率,在可配置的大小限制内生成 GIF,并对成功结果进行缓存。

测试:

  • 新增单元测试,覆盖中继 URL 选择、代理回退行为、预处理媒体对中继设置的透传,以及在大小限制下的 GIF 压缩。
  • 更新基于 ffmpeg 的发送方测试,以反映扩展后的下载器配置参数。
Original summary in English

Summary by Sourcery

Fix media downloading regression by wiring reverse-proxy support into the media downloader and add size-limited GIF compression for generated GIF variants.

Bug Fixes:

  • Restore sending of all media types by correctly handling image/media relay parameters in the media downloader and falling back to origin when relay fetch fails.

Enhancements:

  • Add reverse-proxy URL construction and selection logic so image and non-image media can optionally be fetched via configurable relay bases while preserving original URLs for caching.
  • Introduce a GIF transcoding helper that incrementally reduces resolution and frame rate to produce GIFs under a configurable size limit with caching of successful results.

Tests:

  • Add unit tests covering relay URL selection, proxy fallback behavior, prepared media passthrough of relay settings, and GIF compression under size limits.
  • Update ffmpeg-based sender tests to reflect the extended downloader configuration parameters.

反代(修复严重回归):
- #74 给 base_sender 加了反代管线,把 image_relay_base_url/media_relay_base_url
  传给 MediaDownloader.get_or_download(_prepared),但下载器从未接收这两个参数,
  导致每次媒体下载 TypeError 被吞、download_failed=True,所有媒体静默发不出去。
- 在 get_or_download / get_or_download_prepared 加这两个参数并真正接通:新增
  _build_relay_url(支持 https://wsrv.nl/https://wsrv.nl/?url= 两种形式)和
  _select_relay_base(图片优先图片反代、非图片走通用反代),在抓取处"先反代再
  回源"。缓存 key 与 original_url 始终保持原始 URL,m3u8 不走反代,两个反代为空
  时行为与之前一致。

ffmpeg:
- 新增 FFmpegTool.transcode_to_gif_under_limit,逐步降分辨率/帧率把视频转为不超过
  指定字节数的 GIF;media_downloader 在生成 GIF 变体时调用。

测试:
- 新增反代回归测试与 GIF 压缩测试;修正 test_base_sender_ffmpeg 中因 #74 起就过时
  的预期 call dict(补 relay 键)。
Copilot AI review requested due to automatic review settings June 1, 2026 00:21
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8ca86a4a-a78b-440c-8ef5-d2fff75f7979

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Jun 1, 2026

审阅者指南

在 media_downloader 中端到端实现媒体反向代理(中继)支持,修复了由于意外的中继 kwargs 导致媒体无法发送的回归问题,并通过 ffmpeg 新增了带大小限制的 GIF 压缩流程以及相应测试,并调整了 sender 侧的预期。

带中继与回退逻辑的媒体下载时序图

sequenceDiagram
    actor Sender
    participant MediaDownloader
    participant _select_relay_base
    participant _build_relay_url
    participant download_to_temp

    Sender->>MediaDownloader: get_or_download_prepared(..., image_relay_base_url, media_relay_base_url)
    MediaDownloader->>MediaDownloader: get_or_download(..., image_relay_base_url, media_relay_base_url)
    MediaDownloader->>_select_relay_base: _select_relay_base(media_type, image_relay_base_url, media_relay_base_url)
    _select_relay_base-->>MediaDownloader: relay_base
    MediaDownloader->>_build_relay_url: _build_relay_url(relay_base, url)
    _build_relay_url-->>MediaDownloader: fetch_url

    alt fetch_url != url
        MediaDownloader->>download_to_temp: download_to_temp(url=fetch_url, ...)
        opt relay download fails
            MediaDownloader->>download_to_temp: download_to_temp(url=url, ...)
        end
    else fetch_url == url
        MediaDownloader->>download_to_temp: download_to_temp(url=url, ...)
    end

    MediaDownloader-->>Sender: PreparedMedia
Loading

文件级变更

Change Details Files
通过 MediaDownloader 贯通图片/媒体中继基准 URL,构建中继拉取 URL,并在中继失败时回退到源站,同时保持缓存键和 original_url 不变。
  • 新增辅助函数,用于根据媒体类型选择中继基准 URL,并构造最终中继 URL,支持不同的基准 URL 格式(纯 base、以 ?url= 结尾、已带查询参数等)。
  • 为 get_or_download 和 get_or_download_prepared 新增 image_relay_base_url 和 media_relay_base_url 参数,并从调用方向下传递。
  • 调整下载流程:在配置了中继时优先通过中继 URL 拉取;如果中继拉取失败,则记录日志并回退到原始 URL 重试;同时始终使用原始 URL 作为缓存键以及 PreparedMedia.original_url。
  • 确保 m3u8 下载路径不受中继处理影响,并在两个中继基准 URL 都为空时整体行为保持完全一致。
src/infrastructure/media/media_downloader.py
引入 GIF 转码辅助函数,通过迭代降低分辨率和帧率,在字节大小上限内压缩 GIF,并支持缓存和错误处理。
  • 新增 FFmpegTool.transcode_to_gif_under_limit:进行输入校验、准备缓存目录,并在 FPS 与缩放因子组合上迭代尝试,生成小于 max_bytes 的 GIF。
  • 复用已有的 FFmpeg 可用性检查和与 GIF 相关的常量,并基于源文件路径、mtime、大小以及压缩参数构建确定性的缓存键。
  • 实现健壮的子进程处理,包括超时控制、失败输出清理,以及在成功、超限和失败场景下记录调试/警告日志。
src/infrastructure/utils/ffmpeg_helper.py
为中继行为和 GIF 压缩新增聚焦测试,并更新基础 sender 预期以包含中继参数。
  • 新增 media_downloader 测试,验证在各种中继基准 URL 下的中继 URL 构造逻辑、按媒体类型选择中继、在两个中继基准都为空时的行为、中继失败时回退到源站、以及在使用中继拉取的同时 PreparedMedia 仍保留 original_url。
  • 为新的 GIF 压缩辅助函数新增测试(如果在 diff 其他部分存在),确保在大小限制及各种边界条件下能够通过。
  • 更新基础 sender ffmpeg 测试,使期望的 get_or_download 调用包含 image_relay_base_url 和 media_relay_base_url,并移除与旧行为相关的未使用导入。
tests/unit/infrastructure/test_media_downloader.py
tests/unit/infrastructure/test_base_sender_ffmpeg.py

可能关联的问题


提示与命令

与 Sourcery 交互

  • 触发新审阅: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审阅评论。
  • 从审阅评论生成 GitHub issue: 在审阅评论下回复,要求 Sourcery 从该评论创建 issue。也可以直接回复 @sourcery-ai issue,从该评论生成 issue。
  • 生成 pull request 标题: 在 pull request 标题中任意位置写上 @sourcery-ai,即可随时生成标题。也可以在 pull request 中评论 @sourcery-ai title,以(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文的任意位置写上 @sourcery-ai summary,即可在指定位置生成 PR 摘要。也可以在 pull request 中评论 @sourcery-ai summary,以(重新)生成摘要。
  • 生成审阅者指南: 在 pull request 中评论 @sourcery-ai guide,即可随时(重新)生成审阅者指南。
  • 解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,以解决所有 Sourcery 评论。适用于你已处理所有评论且不希望再看到它们的情况。
  • 撤销所有 Sourcery 审阅: 在 pull request 中评论 @sourcery-ai dismiss,以撤销所有现有的 Sourcery 审阅。尤其适用于你希望从头开始新的审阅——别忘了再评论 @sourcery-ai review 以触发新审阅!

自定义你的体验

访问你的 控制台 以:

  • 启用或停用诸如 Sourcery 生成的 pull request 摘要、审阅者指南等审阅功能。
  • 更改审阅语言。
  • 添加、移除或编辑自定义审阅指令。
  • 调整其他审阅设置。

获取帮助

Original review guide in English

Reviewer's Guide

Implements media reverse-proxy (relay) support end-to-end in the media downloader, fixes a regression where media could not be sent due to unexpected relay kwargs, and adds a size-limited GIF compression path via ffmpeg with corresponding tests and adjusted sender expectations.

Sequence diagram for media download with relay and fallback

sequenceDiagram
    actor Sender
    participant MediaDownloader
    participant _select_relay_base
    participant _build_relay_url
    participant download_to_temp

    Sender->>MediaDownloader: get_or_download_prepared(..., image_relay_base_url, media_relay_base_url)
    MediaDownloader->>MediaDownloader: get_or_download(..., image_relay_base_url, media_relay_base_url)
    MediaDownloader->>_select_relay_base: _select_relay_base(media_type, image_relay_base_url, media_relay_base_url)
    _select_relay_base-->>MediaDownloader: relay_base
    MediaDownloader->>_build_relay_url: _build_relay_url(relay_base, url)
    _build_relay_url-->>MediaDownloader: fetch_url

    alt fetch_url != url
        MediaDownloader->>download_to_temp: download_to_temp(url=fetch_url, ...)
        opt relay download fails
            MediaDownloader->>download_to_temp: download_to_temp(url=url, ...)
        end
    else fetch_url == url
        MediaDownloader->>download_to_temp: download_to_temp(url=url, ...)
    end

    MediaDownloader-->>Sender: PreparedMedia
Loading

File-Level Changes

Change Details Files
Wire image/media relay base URLs through MediaDownloader, building relay fetch URLs, falling back to origin on relay failure, while keeping cache keys and original_url unchanged.
  • Add helper functions to select relay base URL based on media type and to construct the final relay URL supporting different base URL formats (bare base, ?url= suffix, existing query).
  • Extend get_or_download and get_or_download_prepared with image_relay_base_url and media_relay_base_url parameters and pass them through from callers.
  • Change download flow to fetch via relay URL when configured, logging and retrying with the original URL if relay fetch fails, while always using the original URL for cache keys and PreparedMedia.original_url.
  • Ensure m3u8 download paths are not altered by relay handling and overall behavior remains identical when both relay bases are empty.
src/infrastructure/media/media_downloader.py
Introduce a GIF transcoding helper that compresses GIFs under a byte-size limit by iteratively reducing resolution and FPS with caching and error handling.
  • Add FFmpegTool.transcode_to_gif_under_limit which validates inputs, prepares a cache directory, and iterates over combinations of FPS and scale factors to produce a GIF under max_bytes.
  • Reuse existing FFmpeg availability checks and GIF-related constants, building deterministic cache keys based on source path, mtime, size, and compression parameters.
  • Implement robust subprocess handling with timeouts, cleanup of failed outputs, and debug/warning logs for success, oversize, and failure cases.
src/infrastructure/utils/ffmpeg_helper.py
Add focused tests for relay behavior and GIF compression, and update base sender expectations to include relay parameters.
  • Add media_downloader tests that verify relay URL construction for various bases, media-type-based relay selection, behavior when both relay bases are empty, relay failure fallback to origin, and that prepared media preserves original_url while using relay for fetches.
  • Introduce tests for the new GIF compression helper (if present in other parts of the diff) and ensure they pass under size constraints and various edge conditions.
  • Update base sender ffmpeg tests so the expected get_or_download call includes image_relay_base_url and media_relay_base_url, and remove unused imports tied to older behavior.
tests/unit/infrastructure/test_media_downloader.py
tests/unit/infrastructure/test_base_sender_ffmpeg.py

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions github-actions Bot added area: backend Backend or core runtime changes area: tests Test changes labels Jun 1, 2026
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我在这里给出一些整体性的反馈:

  • 关于 relay 的辅助函数签名(_select_relay_base_build_relay_url,以及新的 image_relay_base_url / media_relay_base_url 参数)目前假设类型为 str,但在内部是通过 or '' 来处理可能的 None;建议将类型标注调整为 str | None(以及/或者在对外的边界处做一次规范化),这样能更准确地反映实际用法,也能避免调用方和类型检查器产生困惑。
  • FFmpegTool.transcode_to_gif_under_limit 中,你在循环内多次调用 output_path.stat()(包括在已有缓存文件的快速路径上);在每次尝试时只缓存一次文件大小可以简化逻辑,并避免重复的系统调用。
给 AI Agent 的提示
请根据这次代码审查中的评论进行修改:

## 总体评论
- 关于 relay 的辅助函数签名(`_select_relay_base``_build_relay_url`,以及新的 `image_relay_base_url` / `media_relay_base_url` 参数)目前假设类型为 `str`,但在内部是通过 `or ''` 来处理可能的 `None`;建议将类型标注调整为 `str | None`(以及/或者在对外的边界处做一次规范化),这样能更准确地反映实际用法,也能避免调用方和类型检查器产生困惑。
-`FFmpegTool.transcode_to_gif_under_limit` 中,你在循环内多次调用 `output_path.stat()`(包括在已有缓存文件的快速路径上);在每次尝试时只缓存一次文件大小可以简化逻辑,并避免重复的系统调用。

Sourcery 对开源项目是免费的——如果你觉得我们的审查有帮助,欢迎分享给更多人 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进之后的代码审查。
Original comment in English

Hey - I've left some high level feedback:

  • The helper signatures around relays (_select_relay_base, _build_relay_url, and the new image_relay_base_url / media_relay_base_url parameters) assume str but internally handle potential None via or ''; consider adjusting the type hints to str | None (and/or normalizing once at the public boundary) to better reflect actual usage and avoid confusion for callers and type-checkers.
  • In FFmpegTool.transcode_to_gif_under_limit, you call output_path.stat() multiple times inside the loop (including on the fast path where a cached file already exists); caching the size once per attempt would simplify the logic and avoid redundant syscalls.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The helper signatures around relays (`_select_relay_base`, `_build_relay_url`, and the new `image_relay_base_url` / `media_relay_base_url` parameters) assume `str` but internally handle potential `None` via `or ''`; consider adjusting the type hints to `str | None` (and/or normalizing once at the public boundary) to better reflect actual usage and avoid confusion for callers and type-checkers.
- In `FFmpegTool.transcode_to_gif_under_limit`, you call `output_path.stat()` multiple times inside the loop (including on the fast path where a cached file already exists); caching the size once per attempt would simplify the logic and avoid redundant syscalls.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 修复了媒体发送的严重回归:此前 base_sender 通过 **relay_kwargs 传入的 image_relay_base_url / media_relay_base_url 未被 MediaDownloader.get_or_download(_prepared) 接收,导致每次下载抛 TypeError 并被吞掉,媒体静默丢失。同时补齐了 GIF 变体的大小限制压缩能力。

Changes:

  • MediaDownloader 中接通图片/通用媒体反代参数,新增反代 URL 构建与反代基址选择逻辑,并实现“先反代抓取、失败回源直连”的下载策略(m3u8 分支不走反代,缓存 key 保持原始 URL)。
  • FFmpegTool 中新增 transcode_to_gif_under_limit,通过逐步降分辨率/帧率生成不超过上限字节数的 GIF,并在生成 GIF 变体时启用压缩变体(compressed_gif)。
  • 新增/更新单元测试:覆盖反代 base 形式、图片/非图片选择、反代失败回源、prepared 透传,以及更新 ffmpeg sender 相关的预期调用参数。

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
tests/unit/infrastructure/test_media_downloader.py 新增反代行为测试用例,覆盖 URL 构建、选择策略与回源逻辑,并验证缓存 key/original_url 不变。
tests/unit/infrastructure/test_base_sender_ffmpeg.py 更新 prepare_media 相关测试预期,补齐 relay 参数透传的 call dict。
src/infrastructure/utils/ffmpeg_helper.py 新增大小限制 GIF 转码工具 transcode_to_gif_under_limit(多档参数尝试 + 缓存)。
src/infrastructure/media/media_downloader.py 接入 image_relay_base_url/media_relay_base_url 到下载流程,新增 _build_relay_url / _select_relay_base,并在 GIF 变体生成中引入压缩变体。

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- relay 辅助函数与下载器参数(_build_relay_url / _select_relay_base /
  image_relay_base_url / media_relay_base_url)类型标注改为 str | None,
  如实反映内部 `or ""` 容忍 None 的用法。
- transcode_to_gif_under_limit 每次尝试只读取一次 output_path.stat().st_size
  (缓存快速路径与转码后校验各缓存一次),减少重复 syscall。
@FlanChanXwO FlanChanXwO merged commit 57bc6e3 into dev Jun 1, 2026
3 of 4 checks passed
@FlanChanXwO FlanChanXwO deleted the fix/media-relay-and-gif-compress branch June 1, 2026 00:37
@github-project-automation github-project-automation Bot moved this from Todo to Done in astrbot_plugin_rsshub Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: backend Backend or core runtime changes area: tests Test changes

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants