fix(pixiv): capture ugoira frame timings in the post record (ordering bug)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 28s
CI / integration (push) Successful in 3m33s

The core writes the post record BEFORE extract_media, but the ugoira frame
delays were only memoized DURING extract_media — so write_post_record never saw
them and ugoira_frames was always empty in the record. Extract a memoized
_ugoira_meta (frames + zip url share ONE /v1/ugoira/metadata call regardless of
order) and inject client.fetch_ugoira_frames into the downloader (mirrors
Patreon's content_fetcher) so write_post_record populates the frames itself.
Zero extra API calls — the fetch is shared/memoized with extract_media. A
recapture now backfills the timings onto existing ugoira posts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
2026-07-03 19:41:29 -04:00
parent 6c6e8bdb6d
commit f33808b977
5 changed files with 130 additions and 11 deletions
+15 -1
View File
@@ -102,11 +102,16 @@ class PixivDownloader(BaseNativeDownloader):
validate: bool = True,
rate_limit: float = 0.0,
session: requests.Session | None = None,
ugoira_frames_fetcher: Callable[[dict], None] | None = None,
):
super().__init__(
images_root, cookies_path, platform="pixiv",
validate=validate, rate_limit=rate_limit, session=session,
)
# Injected by the ingester (client.fetch_ugoira_frames) so write_post_record
# can populate frame timings — which extract_media memoizes, but the core
# writes the post record FIRST. Mirrors Patreon's content_fetcher.
self._ugoira_frames_fetcher = ugoira_frames_fetcher
if session is None:
# i.pximg.net 403s any GET without the app Referer; mirror the
# client's full app-header profile (gallery-dl serves media off
@@ -248,7 +253,16 @@ class PixivDownloader(BaseNativeDownloader):
"account": user.get("account"),
"name": user.get("name"),
}
# Memoized by extract_media's ugoira branch; the zip has no timings.
# Ugoira frame timings. extract_media memoizes these, but the core writes
# the post record BEFORE extracting media, so fetch them here (shared +
# idempotent via the client's memoization) so the record actually keeps
# them — the zip carries no timings.
if (
work.get("type") == "ugoira"
and not work.get("_ugoira_frames")
and self._ugoira_frames_fetcher is not None
):
self._ugoira_frames_fetcher(post)
frames = work.get("_ugoira_frames")
if frames:
data["ugoira_frames"] = frames