DEV Community

Cover image for How I Packaged 39 Assets For Adobe Vecteezy And Dreamstime With Four Python Scripts

How I Packaged 39 Assets For Adobe Vecteezy And Dreamstime With Four Python Scripts

How I Packaged 39 Assets For Adobe Vecteezy And Dreamstime With Four Python Scripts

The moment that broke me was a rejection email from Adobe Stock at 11pm on a Tuesday. Not a rejection of the art. A rejection of the CSV. I had generated 30 images over two weeks on a server with 1 vCPU and 2 GB RAM, no GPU, and I uploaded them all with a metadata file that had the columns in the wrong order. Every single one bounced. The review system does not warn you, it does not partially accept, it just rejects the batch and you start over.

That night I stopped generating art and started writing scripts. Four of them, as it turned out. Here is what I learned about why metadata, tracking, and packaging are the actual bottleneck, not the images.

The CSV header is a hard gate, not a suggestion

All three platforms I sell on reject a wrong CSV header instantly. Adobe wants a 5-column format. Vecteezy wants 4 columns. Dreamstime wants 15. If your header does not match exactly, the upload fails before a human ever looks at your work.

So the first script, make_metadata.py, writes the header each platform demands and nothing else. You point it at a folder of files:

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
python make_metadata.py ./my-assets --platform dreamstime --out dreamstime.csv --ext .jpg
Enter fullscreen mode Exit fullscreen mode

It uses only the Python standard library, no pip install at all. If you pass a titles.json, it uses your real titles and keywords. Without it, it title-cases the filename. That default is fine for a first pass, but replace it before you submit, because a filename like abstract_gradient_04 as a title reads exactly like what it is.

Double-posting is the fastest way to get flagged

Here is the mistake nobody warns you about. You run a batch of 20 uploads, the connection drops at item 14, and you re-run the script. Now you have duplicates, and duplicate submissions are one of the quickest ways to get an account flagged on stock platforms.

My fix is embarrassingly simple. upload_tracker.py is a ledger:

python upload_tracker.py pending items.txt
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Enter fullscreen mode Exit fullscreen mode

Before any batch, I ask it what is pending. After each successful upload, I mark it done. A re-run now skips anything already posted. It is maybe 80 lines of standard library code and it has saved me from at least three duplicate disasters since.

A document is not a product until it looks like one

The middle two scripts solve a different problem. I had Markdown content that needed to become a sellable PDF, and I had asset bundles that needed to become actual products.

md2pdf.py takes one command:

python md2pdf.py content.md product.pdf "My Product Title"
Enter fullscreen mode Exit fullscreen mode

It renders headings, tables, lists, and links into a clean PDF using reportlab. That is one of only two third-party packages in the whole kit. The other is Pillow, used by pack_product.py to render a 1280x720 cover and a 600x600 thumbnail, then zip everything up:

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

I learned this lesson the hard way. The same files sold noticeably better once they shipped as a zip with a real cover instead of a bare file. Buyers judge the package before they open it.

The 2 GB RAM rules that made this possible

Everything above ran on 1 vCPU, 2 GB RAM, no GPU. That constraint forced habits I now consider non-negotiable:

One process at a time. Never fan out image jobs in parallel on a single-core box. Call gc.collect() after each asset to free memory between items. Measure peak RSS with resource.getrusage(RUSAGE_SELF).ru_maxrss, not average usage, because peak is what kills you. For heavy render steps, run one file per subprocess so memory actually returns to the OS. And render at the resolution you need: inspecting a preview at 60 dpi instead of 300 saves an enormous amount of RAM and time.

The total output of this pipeline was 39 finished products, 856 MB, backed by roughly 19,000 lines of pipeline code and 300+ pages of research behind the rules the scripts enforce.

One structural mistake worth copying around

The folder layout matters more than I expected. The structure I settled on:

project/
  assets/       raw output
  output/       ready-to-submit files + metadata.csv
  packs/        finished product zips + covers
  research/     notes and sources
  scripts/      the tools
Enter fullscreen mode Exit fullscreen mode

The one I got wrong: I deleted research/ once to "clean up". Those notes and scrape logs were the raw material for the next three products. Keep that folder forever. Storage is cheaper than redoing work.

Where to get the scripts

I packaged these four scripts as the Pipeline Starter Kit, because every maker I talk to stalls on the same four things: metadata headers, double-posting, turning docs into products, and packaging. The kit is the exact code that produced those 39 products, install instructions included, and the two metadata and tracking scripts need nothing beyond Python itself.

See the kit


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

Top comments (0)