DEV Community

Cover image for Zip, Cover, Thumbnail: The Packaging Script That Made 39 Products Look Finished**

Zip, Cover, Thumbnail: The Packaging Script That Made 39 Products Look Finished**

I had 40 finished vectors and a CSV I wrote myself. Uploaded to Adobe Stock. Every row bounced. The file had five columns. Adobe wants five columns. Not those five. The header names are not negotiable, and nothing in the rejection told me which name was wrong. I spent a day diffing my header against the template.

That day is why I automate packaging now. Not the art. The last mile, where a folder of files becomes something a platform will accept.

Metadata is a header or it is nothing

Every channel rejects a bad CSV instantly, before it looks at a single asset.

Adobe Stock: 5 columns. Vecteezy: 4. Dreamstime: 15.

The script that fixed this for me writes the exact header each platform demands and nothing else. One folder in, one CSV out:

python make_metadata.py ./my-assets --platform adobe    --out metadata.csv --ext .jpg
python make_metadata.py ./my-assets --platform vecteezy --out vecteezy.csv --ext .jpg
Enter fullscreen mode Exit fullscreen mode

Without a titles file it title-cases the filename and uses that. Fine for a first pass. Replace it before you submit, because the title is what a buyer sees in search, and Asset 001 Final v2 is not a title.

I made 1,700 assets before I understood this. 58 of them are live on Adobe right now. The gap between those two numbers is not the art. It is everything downstream of the art.

Double-posting is how accounts get flagged

Fastest way to get an account flagged is running the same batch twice. It happens easily. A job dies halfway. You fix the bug and re-run. Now 200 files are queued twice.

The fix is a ledger, not discipline. A small file that remembers what you already marked done:

python upload_tracker.py pending items.txt   # what is NOT done yet
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Enter fullscreen mode Exit fullscreen mode

A re-run reads the ledger and skips. No memory required. It uses the standard library alone, so there is nothing to install. This script has saved me more account health than any keyword trick.

A zip with no cover is not a product

Bare files sell badly. A zip with a real cover sells. Two images do the work:

  • Cover, 1280 x 720
  • Thumbnail, 600 x 600
python pack_product.py my-product "My Product" "A short subtitle" 9
Enter fullscreen mode Exit fullscreen mode

That writes the zip, the cover, and the thumbnail into a _system folder. Point it elsewhere with --dir or PACK_ROOT. Cover and thumbnail render with Pillow, the only image dependency in the kit.

Result so far: 39 products packaged, 856 MB of output. First sale was $0.99. That number is real and it is small. I keep it because it is the honest one.

It ran on 1 vCPU, 2 GB RAM, no GPU

Every script in this pipeline ran on a single-core box with 2 GB of memory and no GPU. That forces habits that are correct anyway:

  • One process at a time. Never fan out image jobs on one core.
  • gc.collect() after each asset.
  • Measure peak, not average: resource.getrusage(RUSAGE_SELF).ru_maxrss is the real ceiling.
  • Heavy render steps get their own subprocess, so memory goes back to the OS when the step ends.
  • Render at the resolution you need. Preview at 60 dpi is enough for a sanity check. Do not render at 300 to eyeball it.

Generated source images cost money per call. On a small box you cannot afford to re-roll 1,700 assets because a batch died at asset 900 with the state held in RAM. The ledger plus one-process rule is what makes a crash cost one asset instead of a night.

Review is the filter, volume is the input

1,700 made. 58 live. That ratio looks bad until you understand what review does. Platforms check each submission for keywords, category fit, releases, and whether the file matches its own metadata. A weak batch does not fail loudly, it fails one row at a time, days later.

So the pipeline is built to make the reject reason obvious and cheap to fix. Fixed headers mean a rejection is about the asset, not the CSV. The ledger means a retry is safe. Covers mean a finished product instead of a folder.

Two third-party packages in the whole kit: Pillow for covers, reportlab for turning a Markdown doc into a PDF product. Everything else is standard library. That is deliberate. On a 2 GB box, fewer dependencies is fewer failures.

What this actually is

Everything above is the packaging layer of a larger pipeline. I wrote about 19,000 lines of pipeline code around it, backed by 300+ pages of platform rules and notes, and this kit is the part that does the visible work: metadata, ledger, PDF, zip, cover, thumbnail.

Digital Asset Factory is the full system, built from this same work. If the header rejections and the double-posting sound familiar, that is what it solves first.

Get digital asset factory


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

Top comments (0)