DEV Community

Cover image for Why pack_product.py Renders Both a 1280Ã 720 Cover and a 600Ã 600 Thumbnail

Why pack_product.py Renders Both a 1280Ã 720 Cover and a 600Ã 600 Thumbnail

First pack I shipped was a zip and a 1280x720 cover. Clean render, centered title, subtitle underneath. Storefront grid showed it as a square. The crop took the middle of the frame and cut the subtitle off at both edges. Nobody complained, because nobody bought it. First actual sale came much later, $0.99, after 58 assets went live on Adobe out of roughly 1,700 I had rendered by then. The cover was not the reason for the wait, but it was the reason the listing looked unfinished next to everyone else's.

So pack_product.py now writes two images per product. Here is what each one is for and why it comes out of the same pass.

Why one size is not enough

A 16:9 frame and a square frame get cropped by different containers. A banner, a store header, a product page hero: all wide, all 16:9-ish. Grids, tiles, thumbnails, search results: all square. If I render one image and let the platform crop it, I lose control of where the crop lands. Text near the edge is the first thing to go.

Two renders means two decisions. The wide one keeps the title at reading size with the art visible behind it. The square one drops the subtitle entirely, because a subtitle at 600x600 is four pixels tall and reads as noise. Same art, different composition.

What each size holds

1280x720, the cover. 16:9, the shape a product page hero wants. Title across the lower third, subtitle under it, art fills the frame. This is the file I put first in a listing and the one I use when I link a pack anywhere.

600x600, the thumbnail. Square. Title only, larger, centered. The art gets a square cover crop from the center of the source image, so the useful part of the art stays in frame. No subtitle, no small type, nothing that dies when the tile shrinks on a phone.

Both are JPEG. Filenames are <slug>-cover.jpg and <slug>-thumb.jpg, written next to the zip. That naming matters downstream: make_metadata.py picks files up by extension, so the same folder can feed an Adobe 5-column CSV, a Vecteezy 4-column CSV, or a Dreamstime 15-column one without me renaming anything by hand.

One render pass, two files

The script reads the source art once. It resizes to 600x600 with a center cover crop, draws the title, saves the thumbnail. Then it builds a 1280x720 canvas in the background color, pastes the composed square region back in at natural size, draws the title and subtitle at wide-layout positions, and saves the cover. Source read is one, disk writes are two.

That ordering is deliberate. Doing the square first means the crop decision is made once and the wide frame is built around it, so the two images never disagree about where the subject is.

I ran this on 1 vCPU, 2 GB RAM, no GPU, no parallel workers. Pillow holds one image at a time; I do not open the source twice and I do not keep both full-size canvases alive after saving. gc.collect() runs after each product. Peak memory is checked with resource.getrusage(RUSAGE_SELF).ru_maxrss, which reports the high water mark instead of an average that hides the spike. Rendering two images per product instead of one raised the ceiling for that step by very little, because the second image is composed from pixels already in memory.

What I stopped rendering

I used to render a preview at the largest size I could, then downscale. That was wasted work. A preview is for eyeballing placement, and 60 dpi is enough for that. Full-resolution previews only get rendered when I actually need to look at fine detail, which is almost never for a cover.

I also stopped rendering covers for products I had not finished. 39 finished products, 856 MB of output, and a stack of orphan cover files I could not match to anything. If the zip does not exist, the cover does not get made.

Where this sits

The cover step is the last thing that runs. Metadata first, because a wrong CSV header gets a batch rejected immediately and wasted renders do not help. Then the PDF if there is a doc. Then pack_product.py zips the files and writes both images into a _system folder, or wherever PACK_ROOT points.

The two-image decision is small. It cost me about twenty lines and one extra save per product. It also means the listing looks finished at every size a platform decides to show it, which is the only part a buyer ever sees.

If you want the whole pipeline rather than the cover logic on its own, this script is one of four in the kit I built from this work: metadata, upload tracking, markdown to PDF, and packaging. Same constraints, same 1 vCPU box, no framework.

Get digital asset factory


Building digital products? Free guides & kits for digital product sellers.

Top comments (0)