DEV Community

Cover image for Adobe, Vecteezy, Dreamstime: Building Exact Upload CSVs From a Folder With Bare Python**

Adobe, Vecteezy, Dreamstime: Building Exact Upload CSVs From a Folder With Bare Python**

The moment this clicked for me was a Tuesday night in front of Adobe Stock's contributor upload screen. I had 12 JPEGs ready, a CSV I'd built by hand in a spreadsheet, and a red error banner telling me the file was invalid. No reason given. Just rejected. I opened the CSV again, counted the columns, and found I had 6 where Adobe wanted 5. That one extra column, probably a trailing keyword field I thought would be helpful, cost me the whole batch and about two hours.

That night I stopped hand-building CSVs and wrote a script instead. This post is what I learned about the three platforms' upload formats and how I now generate all of them from a folder of files with bare Python.

The three headers, exactly

Each platform rejects a wrong header instantly and tells you almost nothing. So the header is not negotiable. Here is what my script writes:

Adobe Stock wants a 5-column CSV: Filename, Title, Keywords, Category, Releases. That's it. One file per row, keywords separated by commas inside a single quoted field, category as a number. If your file has 4 columns or 6, the upload fails before Adobe even looks at your images.

Vecteezy wants 4 columns: file, title, keywords, category. Different names, different order, same idea. The trap here is that people reuse their Adobe CSV and the header names don't match, so everything bounces.

Dreamstime is the painful one at 15 columns. Most of them can be empty for a solo contributor with no model releases, but they have to exist, in the right order, or the row is rejected.

The mistake I see other makers make is maintaining three spreadsheets by hand. I did that for a month. The fix was one script, make_metadata.py, that walks a folder and emits all three formats from the same source 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 standard library. No pandas, no install step. The filename becomes the title (title-cased) unless you pass a titles.json with real titles and keywords. For a first pass the filename fallback is fine, but replace it before you submit, review checks on Adobe do look at title quality and a filename like asset_001_final_v2.jpg does not help you.

Double-posting is the fastest way to get flagged

This one cost a friend of mine a warning on Vecteezy. He re-ran a batch script overnight, forgot he'd already run it, and posted the same 40 files twice. Duplicate submissions get your account flagged faster than almost anything else.

So the second script is dumb on purpose. upload_tracker.py is a plain-text ledger:

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

Before any batch, I generate the pending list from the folder, and the batch only processes what's in it. A re-run can't double-post because done files drop off the pending list. No database, no state to corrupt. It's a text file and it has never failed me.

Where the real bottleneck actually is

Making the art is the fun part and, honestly, the easy part now. The things that stall people, in the order I see them stall:

  1. Metadata formats (covered above).
  2. Double-posting (covered above).
  3. Turning content into a product that looks like one. A bare PDF sells worse than a styled one. My md2pdf.py takes a Markdown file and renders a clean PDF with headings, tables, lists and links using reportlab. One command, one of only two third-party packages in the whole kit.
  4. Packaging. pack_product.py zips the files and renders a 1280×720 cover plus a 600×600 thumbnail with Pillow. A zip with a real cover sells. A bare file does not.

You do not need a big machine

The entire pipeline ran on a 1 vCPU, 2 GB RAM server with no GPU. That constraint forced habits worth keeping even on better hardware:

  • One process at a time. Never fan out image jobs in parallel on a single core.
  • Call gc.collect() between assets.
  • Measure peak RSS with resource.getrusage(RUSAGE_SELF).ru_maxrss, not average usage. Peak is what kills a 2 GB box.
  • Run heavy render steps one file per subprocess so memory actually returns to the OS.
  • Render at the resolution you need. Inspecting a preview at 60 dpi beats rendering at 300.

For context on what this produces: 1,700 assets made, 58 live on Adobe Stock, and the first sale came in at $0.99. Not life-changing money, but it's real, and it came from a pipeline that fits on a tiny server.

The folder layout that keeps it sane

One habit worth stealing even if you write your own scripts:

project/
  assets/       raw output
  output/       ready-to-submit files + CSVs
  packs/        finished zips + covers
  research/     notes, scrape logs, sources
  scripts/      the tools
Enter fullscreen mode Exit fullscreen mode

Keep research/ forever. I deleted mine once to "clean up" and lost the raw material for two future products. Won't repeat that.

Where this all lives now

Everything above is packaged as the digital asset factory: the four scripts (make_metadata.py, upload_tracker.py, md2pdf.py, pack_product.py), the exact scripts that packaged 39 products and 856 MB of output, from a parent project of about 19,000 lines of pipeline code and 300+ pages of research behind the rules the scripts enforce. It's the honest version of this post, the tools rather than just the theory. If you'd rather skip the Tuesday-night error banner, it's here:

Get digital asset factory


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

Top comments (0)