Last March I uploaded a product with a bare zip and no cover image. It sat there for two weeks with zero views. Then I made a second version of the same product, added a proper 1280×720 cover and a 600×600 thumbnail, and the listing finally looked like something a human would click on. Same files inside. The difference was entirely the packaging.
That experience is why pack_product.py renders two images and not one, and I want to explain the actual reasons, because they are not aesthetic reasons. They are platform and infrastructure reasons.
The 1280×720 cover exists because of how marketplaces crop
Adobe Stock preview slots and most marketplace listing pages use wide, landscape-shaped image slots. A 1280×720 cover fills that slot edge to edge. If you upload a square image there, the platform either letterboxes it or crops it, and the crop is never centered the way you want. I learned this by uploading a square cover to a listing and seeing my product title cut in half on the preview.
So the wide render is the listing face. pack_product.py draws the product title and subtitle onto it with Pillow, which is one of only two third-party packages in my whole pipeline. The other is reportlab for PDFs. Everything else, including make_metadata.py and upload_tracker.py, runs on the standard library alone.
The 600×600 thumbnail exists for a different reason. Category grids, search result tiles, and profile galleries are square. If you reuse the 1280×720 image as a thumbnail, downscaling it produces a tiny, unreadable strip of text. Rendering the thumbnail separately at 600×600 means I can stack the title vertically and keep it legible at the size people actually see in a grid.
Two renders, one script, 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. You can redirect it with --dir or the PACK_ROOT environment variable, so no script editing when you reorganize folders.
The zip is not optional either
A bare file does not sell. When I package, the script zips the asset folder alongside the two renders so the download and the listing images ship together. Across my catalogue this added up fast: 39 products packaged, 856 MB of output, all from scripts running on a 1 vCPU, 2 GB RAM server with no GPU.
That server constraint shaped the script more than anything else. On a 1-core box you cannot fan out image jobs in parallel. Pillow renders are fast, but if you run five at once on 2 GB of RAM you will hit the ceiling and the OOM killer will pick whichever process it likes. So pack_product.py does one product at a time, and the surrounding pipeline calls gc.collect() between items and measures peak RSS with resource.getrusage(RUSAGE_SELF).ru_maxrss rather than trusting average memory. Average lies. Peak is what crashes your box.
Packaging is the fourth stall point, and the first three are worse
I built these four scripts because making the art is the fun part and everything after it is where people quit. In order:
- Metadata. Adobe wants a 5-column CSV header, Vecteezy wants 4 columns, Dreamstime wants 15. All three reject a wrong header instantly, before a human ever sees your work.
make_metadata.pywrites exactly the header each platform demands. - Double-posting. Run your upload batch twice and you can get an account flagged.
upload_tracker.pyis a ledger, so a re-run never double-posts. - Turning a doc into a product.
md2pdf.pyconverts Markdown to a styled PDF in one command. - Packaging. The two renders and the zip, which is what this post is about.
The metadata point deserves a number. My pipeline has produced about 1,700 assets and 58 are live on Adobe right now. My first sale was $0.99. I am not telling you this as a flex. I am telling you that the boring layer, the CSVs and the covers, is what moved files from a local folder onto a marketplace where someone could actually buy one.
Render at the resolution you need
This habit applies to the covers too. Inspecting a preview at 60 dpi beats rendering it at 300. For the cover and thumbnail, the target dimensions are fixed by the platforms, so there is no reason to render larger and downscale. 1280×720 and 600×600 are the sizes, rendered once, saved once. On a 2 GB box, rendering at the resolution you need is the difference between a batch that finishes and a batch that dies at item 30.
For heavy render steps elsewhere in the pipeline I go further and process one file per subprocess, so memory is actually returned to the OS between items. Pillow operations inside pack_product.py are small enough that this is not needed there, but the principle is the same: on a tiny server, plan for the peak, not the average.
The folder layout that keeps it sane
None of this works without a fixed structure. The kit assumes:
project/
assets/ raw output (one file per asset)
output/ ready-to-submit files + metadata.csv
packs/ finished product zips + covers
research/ your notes and sources
scripts/ these tools
One warning from personal failure: keep research/ forever. I once deleted my scrape logs and notes to "clean up" and lost raw material for three future products. Never again.
Where this came from
These scripts are not a theory. They packaged 39 products, they run on 1 vCPU and 2 GB RAM with no GPU, and they sit inside roughly 19,000 lines of pipeline code backed by 300+ pages of research on the platform rules they enforce. The whole thing, including the prompt engine that feeds the assets, is what I now sell as the digital asset factory. It is the same system, documented and packaged, not a course and not a promise of income. If you want the four scripts plus the surrounding playbook without rebuilding the boring layer yourself, it is here:
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)