Last March I sat on my kitchen floor at 1 a.m. staring at a Gumroad listing I had just published. The product zip was fine. The listing looked broken. My cover image was a screenshot I'd cropped by hand, stretched to fit, and it showed up in the marketplace grid as a blurry smudge next to sellers who clearly had their packaging automated. That was the moment I stopped treating covers as an afterthought and wrote pack_product.py to render both a 1280×720 cover and a 600×600 thumbnail from the same source, in the same run, every time.
Here is why those two sizes, and why both come out of one script.
One source, two aspect ratios, zero hand-cropping
The 1280×720 cover is a 16:9 landscape. That ratio works as a wide banner on a product page and it holds up when a marketplace shows your item large. The 600×600 thumbnail is square, because grids and category tiles almost always crop square. If you only ship the wide cover, the platform will center-crop it to fill the square slot, and you lose the edges of your composition. Your title text gets clipped. Your subject gets pushed off-frame. I had this happen on my first Adobe submission batch: the wide cover looked great on the product page, and in the grid view the keyword strip I'd placed on the left third was simply gone.
So the script renders twice from the same template. The cover gets the full layout. The thumbnail gets a re-composed square version, with the title re-centered, not just cropped. Same fonts, same palette, same text, two canvases. Pillow does both. That is one of only two third-party packages in my whole packaging layer, the other being reportlab for PDFs.
Why automation here matters more than it seems
When I packaged my first 39 products, roughly 856 MB of output, I did the covers manually for the first dozen. It took maybe ten minutes per product. Automated, it takes seconds, and the real saving is not time. It is consistency. A catalogue where every cover uses the same layout reads as one brand. A catalogue where each cover was made by hand on a different tired evening reads as noise.
There is a second reason: reproducibility. When I update a product, the script regenerates both images identically, so nothing drifts. If I change the subtitle or the price shown on the cover, I rerun one command:
python pack_product.py my-product "My Product" "A short subtitle" 9
That writes my-product.zip, my-product-cover.jpg, and my-product-thumb.jpg into a _system folder. I never touch an image editor for packaging anymore.
The memory reality on a tiny box
This whole pipeline runs on 1 vCPU and 2 GB RAM with no GPU. Rendering images is the heaviest thing the packaging step does, so a few habits matter. Render at the resolution you need, and no more. There is no reason to rasterize a 3000-pixel intermediate just to downscale it to 600. I call gc.collect() after each asset, and I measure peak RSS with resource.getrusage(RUSAGE_SELF).ru_maxrss, not average usage, because the peak is what gets your process killed. One process at a time, never parallel image jobs on a single core. Two 1280×720 renders plus a 600×600 render per product fits comfortably in that budget. Fanning out across threads does not.
The marketplace side of it
The packaging layer sits next to the metadata layer, and they follow the same philosophy: the platform's format is not negotiable, so the script writes exactly that and nothing else. Adobe's upload CSV wants a 5-column header. Vecteezy wants 4 columns. Dreamstime wants 15. A wrong header gets rejected instantly, no partial credit. Covers are the same kind of constraint. The platform decides what shape it displays. You either render to spec or you let the platform crop you at random.
For the record, my numbers are small and real: around 1,700 assets made, 58 live on Adobe Stock, and my first stock sale was $0.99. I am not claiming covers made me rich. I am claiming that a $0.99 sale on a listing with a clean, machine-consistent cover beats no sale on a listing with a blurry screenshot.
What I would tell the version of me from last March
Do not hand-make covers. Not for the first product, not for the fiftieth. Write the renderer once, render both sizes from one source, and make packaging a command you run, not a session you dread. The other habits that survived contact with reality: keep a fixed folder layout (assets/, output/, packs/, research/, scripts/), keep your research notes forever because they are raw material for future products, and track what you have uploaded so a re-run never double-posts, which is the fastest way to get a stock account flagged.
Everything in this post is a script I actually run. pack_product.py is one of four standalone tools in the pipeline starter kit, alongside make_metadata.py for the Adobe, Vecteezy, and Dreamstime CSVs, upload_tracker.py for the ledger, and md2pdf.py for turning Markdown into a styled PDF. They are the packaging layer under the larger system, the digital asset factory, which is the full pipeline that produced those 1,700 assets on a 1-core, 2 GB server. If you want the same scripts instead of rebuilding them from this description, it is here:
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)