Skip to content

Align content-type media caps with official API limits; block MOV on Bluesky - #353

Open
paulocastellano wants to merge 9 commits into
mainfrom
align-platform-media-limits
Open

Align content-type media caps with official API limits; block MOV on Bluesky#353
paulocastellano wants to merge 9 commits into
mainfrom
align-platform-media-limits

Conversation

@paulocastellano

@paulocastellano paulocastellano commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Why

A ~990 MB Instagram video was failing at publish: Meta pulls the file from video_url and its Reel ceiling is 300 MB, but ContentType::InstagramReel advertised 1 GB. While closing that gap, the rest of the per-content_type caps were audited against the official API pages the publishers actually call, and the caps are now enforced server-side too — the editor was the only thing checking size and duration, so the REST API and MCP could store media the network would reject.

What changes (effective on Cloud, after the 10 MB image / 1 GB video hard cap)

content_type Before After (official)
instagram_reel video 1 GB 300 MB (15 min unchanged)
x_post video 512 MB / 140 s (dm_video) 8 GB / 20 min (tweet_video default) → effective 1 GB
bluesky_post 100 MB video / 60 s, no image cap 2 000 000 B image / 300 000 000 B video (lexicon decimal), 10 min, MOV blocked
tiktok_video no size, no duration 4 GB → effective 1 GB, 10 min (creator_info may lower it at publish)
tiktok_photo image none 20 MB → effective 10 MB
discord_message none 20 MB image (eff. 10 MB) / 20 MB video
telegram_post none 5 MB image / 20 MB video (URL send path)

Everything else (IG feed/story 100 MB / 60 s, Threads, Facebook, LinkedIn, YouTube, Pinterest, Mastodon) stays as-is.

Publisher fixes

  • Bluesky: trypost.platforms.bluesky.video_max_bytes default 100 MiB → 300 000 000 so the publisher no longer silently strips a video the editor accepted. ContentTypeTest pins config >= ContentType::BlueskyPost->maxVideoBytes(). Comments in BlueskyPublisher now reflect the MP4-only lexicon.
  • X: XPublisher::getMediaCategory() always sends tweet_video; the > 15 MB → amplify_video branch is gone (amplify_video is the Ads-creative category).
  • The Telegram MediaOptimizer change from the first revision was dead code (no caller) and is dropped.

Server-side enforcement (ContentTypeCompatibleWithMedia)

Runs on publish/schedule for web (UpdatePostRequest), REST (PUT /posts/{post}), and MCP (update-post-tool with scheduled, publish-post-tool). In addition to the existing kind / mixed-media / PDF checks it now rejects:

  • GIF on a type where acceptsGif() is false (this used to be editor-only).
  • MOV on Bluesky (acceptsMov(), Media\Type::isMov() — MIME or .mov extension).
  • Size over maxImageBytes() / maxVideoBytes() / maxDocumentBytes(), read from the snapshot's size. Items without size are not checked (legacy snapshots).
  • Duration over maxVideoDurationSec() when the snapshot carries meta.duration (measured client-side on dashboard uploads; API uploads have no duration — best-effort by design).

Media snapshots written by MediaAttacher, Api\PostController@attachMedia and AttachMediaFromUploadTool now carry size (the asset path already did).

accepts_mov is exposed next to accepts_gif in mediaRules() / toListingArray() (Inertia, GET /content-types, list-content-types-tool); the editor emits mov_not_allowed / no_movs in all 16 locales.

Global upload allow-list is unchanged (MP4 + MOV still accepted everywhere else). No transcoding, no Instagram resumable upload — out of scope.

Tests

  • ContentTypeCompatibleWithMediaTest: GIF accept/reject per type, image/video/PDF size caps (incl. the 2 000 001-byte Bluesky edge proving decimal not MiB), size-less items skipped, duration over/at cap/unknown/no-cap.
  • ContentTypeTest: new caps, Bluesky decimal constants, config-vs-enum coherence.
  • Endpoint level: web UpdatePostRequestTest (MOV rejected, oversize image rejected, draft not blocked), API PostApiPlatformMetaTest (stored MOV on Bluesky, GIF on LinkedIn, 900 MB Reel), MCP PostPlatformMetaToolTest (stored MOV, 900 MB Reel).
  • PostMediaApiTest / MediaUploadFlowTest assert the snapshot size. XPublisherTest asserts tweet_video above 15 MB.
  • Full suite: 4489 passed. Pint, vue-tsc, eslint clean.

Docs PR: trypostit/trypost-docs#22

…on Bluesky

Instagram Reel drops to 300 MB (Meta video_url pull), X Post rises to
1 GB / 20 min (tweet_video, not dm_video), Bluesky moves to 2 MB image /
300 MB video and rejects MOV (lexicon is MP4 only). TikTok, Discord and
Telegram get explicit caps (Telegram uses the URL path: 5 / 20 MB), and
the Telegram image optimizer budget follows to 5 MB.

The MOV gate mirrors the GIF gate: ContentType::acceptsMov(), an
accepts_mov flag in mediaRules()/toListingArray(), the editor warning in
useMedia, and ContentTypeCompatibleWithMedia for API/MCP.
- Bluesky: lexicon decimal bytes (2 000 000 / 300 000 000), 10-minute video
  limit, publisher skip threshold default raised to match the enum (a test
  pins config >= enum); publisher comments reflect the MP4-only lexicon.
- X: 8 GB tweet_video default entitlement; XPublisher always uses
  tweet_video (amplify_video is the Ads-creative category).
- TikTok: 10-minute developer-upload ceiling (creator_info may lower it).
- ContentTypeCompatibleWithMedia now rejects GIF on types that do not
  accept it, and enforces byte caps (via snapshot size) and video duration
  (via client-measured meta.duration when present) on publish/schedule for
  web, REST API and MCP.
- Media snapshots written by MediaAttacher, Api\PostController@attachMedia
  and AttachMediaFromUploadTool now carry size.
- Drop the dead Telegram MediaOptimizer change.
- Tests: rule unit cases (GIF, size, duration, decimal edge), enum caps,
  config-vs-enum coherence, endpoint-level MOV/GIF/size rejection on web,
  API and MCP publish, snapshot size assertions.
…mal units, simplify MOV message

- Chunked upload probes the video's duration client-side and sends it as
  X-Media-Duration; ChunkedAssetReceiver stores it in Media.meta.duration for
  videos. Nothing wrote meta.duration before, so every duration cap (editor
  and ContentTypeCompatibleWithMedia) was inert.
- Byte caps declared in decimal megabytes (Bluesky's lexicon) are rendered in
  decimal units for both the cap and the file, in the editor and the server
  message, so users read "300 MB" instead of "286 MB".
- mov_not_allowed copy shortened in all 16 locales; trimmed the Bluesky
  config comment.
- ContentTypeCompatibleWithMedia: GIF/MOV checks follow the hasImage/hasVideo
  pattern; size/duration loop flattened and messages use interpolation
- useMedia.ts / chunkedUpload.ts restored to house formatting with only the
  needed edits (sizeParams helper, duration header)
- Docblocks and inline comments cut to what the code cannot say itself
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