DEV Community

Cover image for à 720 or 600à 600: What Each Size Does in pack_product.py and Why

à 720 or 600à 600: What Each Size Does in pack_product.py and Why

My first 40 uploads had no cover. I zipped the files, ran the batch, pushed them out. Every one of those packs looked like a folder someone forgot to finish. No preview grid image, no hero. Buyers saw a filename. First sale came later, on a pack that had both images, and it was $0.99. Small number, but it told me the packaging was doing work the zip alone could not.

That is why pack_product.py renders two images and not one: a cover at 1280×720 and a thumbnail at 600×600. Different jobs, different shapes. Here is what each one actually does and why I stopped trying to make one image cover both.

Two images, two jobs

A marketplace grid is not a product page. Grid slots get cropped to a square. Product page hero slots are wide. If you hand the same file to both, one of them gets letterboxed or center-cropped and your title text ends up cut in half.

So the script writes both, every run:

  • my-product-cover.jpg at 1280×720
  • my-product-thumb.jpg at 600×600

Same source art, two crop decisions. The cover keeps the wide composition. The thumb is composed for square, because a 16:9 image squeezed into a square slot loses the edges, and the edges are usually where I put the readable title.

1280×720 is the wide one

16:9 is the shape most product pages and preview slots want. 1280 wide is enough that the image stays sharp on a 1366 px laptop without being heavy. 720 tall keeps the file under a few hundred KB as a quality-85 JPEG, which matters when you are listing hundreds of packs and the upload form is on a slow connection.

At 1280×720 I can fit a title line and a subtitle line without shrinking the type to unreadable. That is the whole reason the script takes three arguments beyond the folder name:

python pack_product.py my-product "My Product" "A short subtitle" 9
Enter fullscreen mode Exit fullscreen mode

The 9 is the price. The title and subtitle get drawn onto the cover. If I tried to fit that same text into 600×600 without reflowing, it would eat the art.

600×600 is the grid one

Square is the safe shape almost everywhere. Grid tiles crop to square, avatar-style slots crop to square, and a square thumbnail stays readable when the platform renders it at 200 px. A 600×600 source downsampled to 200 is a clean 3:1 reduction. A 1280×720 downsampled into a 200×200 square is a crop plus a rescale, and the result looks like a mistake.

600 is also the smallest size where fine text in a thumbnail still resolves after the platform's own compression. I tested smaller. It did not survive.

So the rule in my head is simple. Wide grid or hero, use the cover. Square tile or list row, use the thumb. Never swap them.

What both renders cost on 1 vCPU and 2 GB

This pipeline ran on 1 vCPU, 2 GB RAM, no GPU. Pillow is the only image dependency in pack_product.py, plus reportlab for the PDF path. Two third party packages total for the whole kit. make_metadata.py and upload_tracker.py use the standard library alone, no install.

Rendering two JPEGs per pack is cheap, but it is not free, and two habits keep it inside the box:

  • One process at a time. Never fan out image jobs. On one core, parallel jobs just queue and each one holds its own memory.
  • gc.collect() after each asset. Pillow buffers do not always come back fast enough on their own.

Measure peak RSS, not average:

resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
Enter fullscreen mode Exit fullscreen mode

Average memory tells you nothing about whether the box will survive item 300. Peak tells you the real ceiling. For heavy render steps, one file per subprocess so the memory goes back to the OS.

Also render at the resolution you need. Inspecting a preview at 60 dpi beats rendering it at 300 dpi and then throwing it away.

One command, deterministic names

pack_product.py writes the zip, the cover and the thumb into a _system folder next to where you run it. Three files, fixed names, so a re-run overwrites instead of piling up cover-final-v2.jpg. Point it elsewhere with --dir or the PACK_ROOT environment variable. No editing the script.

That determinism matters more than it sounds. I have 1,700 assets made so far and 58 live on Adobe. The ones that stalled were the ones where I had to guess which cover was current. Fixed names remove the guess.

Do not re-render what you already shipped

The other half of this is not doing work twice. upload_tracker.py is the ledger:

python upload_tracker.py pending items.txt
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Enter fullscreen mode Exit fullscreen mode

Running a batch twice is the fastest way to get an account flagged. The tracker makes it structurally hard. Covers follow the same logic. If the pack exists in packs/ and the ledger says done, the script does not need to touch it again.

Keep research/ forever, too. Notes and scrape logs are raw material for the next product. Deleting them to clean up is the mistake this project made once.

Where this fits

The two sizes are a small decision, but they are the kind that decides whether a pack looks finished. A zip with a real cover and a real thumbnail sells. A bare file does not. That is the whole argument for both images, and for the memory habits that let a 1 vCPU box render them all night.

This came out of the same pipeline that packaged 39 products, 856 MB of output, on 1 vCPU and 2 GB, no GPU. Roughly 19,000 lines of pipeline code sit above these four scripts, and about 300 pages of research behind the rules they enforce.

If you want the working tools instead of rebuilding them: make_metadata.py writes the exact Adobe 5 column, Vecteezy 4 column and Dreamstime 15 column headers the upload forms demand, upload_tracker.py stops double posting, md2pdf.py turns a doc into a product, and pack_product.py makes the cover and thumb above. Two dependencies. Standard library for the rest.

Get digital asset factory


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

Top comments (0)