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
It writes three files into _system next to where you run it:
-
my-product.zipwith the deliverables -
my-product-cover.jpgat 1280Ã720 -
my-product-thumb.jpgat 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
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.
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)