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

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

First product I packed was a set of 24 sticker PNGs. I rendered one image, 1280×720, and used it everywhere. Uploaded to a grid listing. The grid cropped it to a square and my title text, sitting in the left third, disappeared. Nobody told me. I found out when a buyer asked what the product actually was.

That day cost me a full evening. The fix was not a smarter crop. It was rendering two images from the start.

One image cannot serve two shapes

A storefront uses two very different crops.

The wide card on a product page is roughly 16:9. It has room for a title, a subtitle, and a visual. That is what a 1280×720 render is for. It carries your words.

The grid tile is square. Adobe's contributor listing, Vecteezy search results, Gumroad's product grid, all of them render items in a square cell. If you feed a 16:9 image into a square cell, something gets cut. Usually the thing you cared about.

You can crop a 1280×720 down to 600×600. You lose 680 pixels of width. That is more than half the image. Text stretches or dies.

So pack_product.py renders both, from the same source layout, at the size each one needs. No crop, no stretch, no guessing.

What pack_product.py actually does

One command:

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

It writes three files into _system next to where you run it:

  • my-product.zip with the deliverables
  • my-product-cover.jpg at 1280×720
  • my-product-thumb.jpg at 600×600

Change the output location with --dir or the PACK_ROOT environment variable. No editing the script, no hardcoded paths in your repo.

Pillow is the only third party dependency here. That is deliberate. One image library, one install, nothing else to break on a fresh box.

The cover draws the seed, the title, and the subtitle into a wide layout with margins tuned for 16:9. The thumbnail redraws the same content with tighter margins and a bigger relative title, because at 600×600 the viewer is looking at a small tile in a busy grid. Shrinking the cover would make the text unreadable at that size. Redrawing keeps it legible.

Memory on a 1 vCPU / 2 GB box

The whole pipeline runs on one vCPU, 2 GB RAM, no GPU. That constraint shaped every decision in the packer.

Three habits matter:

One process at a time. Never fan out image jobs in parallel on a single core. You get context switching and a memory spike, not speed.

Free between items. Call gc.collect() after each asset. Pillow holds image buffers longer than you think.

Measure peak, not average. resource.getrusage(RUSAGE_SELF).ru_maxrss tells you the real ceiling your process hit. Average RSS looks fine right up until the OOM killer arrives.

Render at the resolution you need. The first version of my cover code drew a huge canvas and downscaled it. On 2 GB that was the difference between finishing a batch and losing it. Draw at 1280×720 and 600×600 directly. Inspecting a preview at 60 dpi beats rendering it at 300.

Numbers after 39 packs

The pipeline has packaged 39 products, 856 MB of output. I have made about 1,700 assets. 58 are live on Adobe. My first sale was $0.99.

Those numbers are small on purpose. I am telling you where the ceiling was, not where it wasn't. The two render sizes were not an optimisation. They were the fix for a real defect that showed up on product one.

Package make_metadata.py alongside it and the same folder produces your upload CSVs. The Adobe header is 5 columns, Vecteezy is 4, Dreamstime is 15. Get the header wrong and the channel rejects the file before a human ever sees it. upload_tracker.py stops a re-run from double-posting, which is the fastest way to get an account flagged. md2pdf.py turns a Markdown doc into something that looks like a product instead of a text file.

All of it runs in the same folder layout:

project/
  assets/     raw output
  output/     ready-to-submit files + CSV
  packs/      zips + covers
  research/   your notes
  scripts/    these tools
Enter fullscreen mode Exit fullscreen mode

Keep research/. I deleted mine once to clean up. That was a mistake I have not repeated.

Where this fits

The two render sizes are a small thing. They are also the kind of small thing that decides whether your listing looks like a product or a file drop.

I built the whole system around this work: metadata headers, the upload ledger, the PDF step, the packer, plus the orchestration and LLM routing layer on top. It runs on the same 1 vCPU / 2 GB box. It is not a tutorial that ends at "here is how you would do it". It is the working set.

Get digital asset factory


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

Top comments (0)