Skip to content

fix(tiktok): resize oversized photos before PULL_FROM_URL - #120

Merged
paulocastellano merged 6 commits into
mainfrom
fix/tiktok-photo-resize
Jun 26, 2026
Merged

paulocastellano merged 6 commits into
mainfrom
fix/tiktok-photo-resize

Conversation

@paulocastellano

Copy link
Copy Markdown
Contributor

Problem

A production post failed to publish to TikTok with "Image dimensions exceed limits" (picture_size_check_failed). The image was 1254×1254; TikTok caps photo width at 1080px (confirmed by postiz's own error map: "Picture must not exceed 1080p").

Root cause

TikTok photo posts use source: PULL_FROM_URL with the raw stored media URLs — TikTok fetches the bytes from us, so we never hold them and the photos never went through MediaOptimizer like every other image publisher (X, Bluesky, LinkedIn, Pinterest, Mastodon, Discord) does. The Platform::TikTok => ['max_width' => 1080] config in MediaOptimizer was effectively dead code.

Fix

For each TikTok photo:

  • within spec (width ≤ 1080) → publish the original URL unchanged (today's behavior, zero overhead);
  • oversized, or unknown dimensions → download → optimizeImage(TikTok) → host the resized copy on the default disk → hand TikTok that URL. The derivative is pruned in a finally once waitForPublishStatus confirms TikTok has pulled it.

This reuses the exact hosted-derivative pattern Instagram/Threads already use in CropsImageForAspectRatio (download → process → Storage::put → Storage::url) — just resize instead of crop. The TikTok max_width config is now actually applied.

Changes

  • TikTokPublisher::publishPhotos — resolve compliant vs oversized, host derivative for oversized, prune after publish.
  • MediaOptimizer::maxWidthForPlatform() — single source of truth for the 1080 cap.
  • MediaItem DTO — carries meta and exposes width()/height() so dimensions are checked without a download.

Tests

  • Oversized (1254w) → TikTok is handed the hosted derivative (social-tiktok-photos/), never the raw URL; derivative is pruned afterward.
  • Compliant (1080×1920) → original URL published unchanged, nothing downloaded or hosted.
  • Unknown dimensions → safe fallback hosts a compliant copy.
  • Full Social suite: 310 passing.

Paulo Castellano added 2 commits June 26, 2026 12:23
TikTok photo posts hand the platform our media URLs and let it pull the
bytes, so they never went through MediaOptimizer like the upload-based
publishers do. An image wider than 1080px reached TikTok untouched and was
rejected with picture_size_check_failed ("Image dimensions exceed limits").

Photos within spec still pass through unchanged. Oversized ones (or images
with unknown dimensions) are downloaded, resized to TikTok's 1080px spec,
hosted on the public disk, and that URL is handed to TikTok instead. The
derivative is pruned once TikTok has pulled it.

Reuses the same hosted-derivative pattern Instagram/Threads already use for
aspect-ratio cropping; the TikTok max_width config was previously dead code.
Direct unit tests for the new MediaItem::width()/height() accessors and
MediaOptimizer::maxWidthForPlatform(), plus drop a redundant inline comment.
Paulo Castellano added 4 commits June 26, 2026 12:42
…optimization

Adds a test for the resize download-failure branch, and tightens the resize
mocks to assert optimization is requested for Platform::TikTok specifically.
…publish

The default disk (r2) is configured with throw => true, so Storage::delete
re-throws on a failed delete. Running that inside the finally meant a delete
failure could discard the publishPhotos return value and surface an
already-live TikTok post as a failure (risking a duplicate re-publish).

Move cleanup into a pruneDerivatives helper that swallows storage errors, and
wrap the resize/host step so decoder/storage failures surface as a categorized
TikTokPublishException instead of an uncategorized error.
…se URL

- Multi-image carousel: only the oversized photo is resized, order preserved,
  the compliant one passes through untouched.
- Cleanup runs even when publishing fails (no orphaned derivative).
- A throwing Storage::delete does not turn a successful publish into a failure.
- Replace hardcoded https://open.tiktokapis.com/v2 with
  config('trypost.platforms.tiktok.api') so the fakes can't drift from the
  configured (overridable) host.
… files

Convert TikTokCreatorInfoTest and TikTokPublishExceptionTest to fake against
config('trypost.platforms.tiktok.api') instead of the hardcoded host, matching
the TikTokPublisherTest cleanup. TikTokControllerTest is left as-is: its only
TikTok URL is a Socialite authorize stub, which has no config counterpart.
@paulocastellano
paulocastellano merged commit 80dc1ab into main Jun 26, 2026
2 checks passed
@paulocastellano
paulocastellano deleted the fix/tiktok-photo-resize branch June 26, 2026 16:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant