DEV Community

Cover image for Fixing Metadata Rejections Across Adobe Vecteezy And Dreamstime With One Script

Fixing Metadata Rejections Across Adobe Vecteezy And Dreamstime With One Script

I lost a whole afternoon to a CSV header. Forty vectors, ready to go, and Adobe's uploader rejected the file in about two seconds. The error message was one line about column count. I had built the CSV by hand in a spreadsheet, and I had the columns in the wrong order. Adobe wants five columns. I had six, because I copied a template I found on a forum that was written for a different platform version.

That was the moment I stopped hand-building metadata files. Here is what I learned fixing it, and what I ended up building.

Each platform demands a different header, and they check it first

This is the part nobody warns you about. Before a human reviewer ever looks at your art, an automated check runs on your upload file. If the header is wrong, you get rejected before anyone sees the work.

The three platforms I submit to have three different shapes:

  • Adobe Stock wants a 5-column CSV. Title, keywords, category, releases, and the filename. Get the order wrong and the whole batch bounces.
  • Vecteezy wants 4 columns. Fewer fields, but a different order, so you cannot reuse the Adobe file.
  • Dreamstime wants 15 columns. Fifteen. Most of them are empty for a solo contributor with no releases, but they still have to be there, in the right positions.

So one folder of assets needs three different metadata files. Doing that by hand for 40 files is an hour of spreadsheet work and one tired mistake away from another rejection. Doing it wrong repeatedly is how you get an account flagged for low-quality submissions.

The fix was to stop thinking of metadata as a spreadsheet task and start thinking of it as a build step. make_metadata.py reads a folder of files and emits the exact header each platform demands, nothing more. One command per platform:

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

The script uses only the Python standard library. No install, no dependencies, no API keys. It title-cases the filename as a default title, which is fine for a first pass, but I always pass a titles.json with real titles and keywords before I actually submit. Filenames as titles is how you end up with a listing called "Floral Pattern 04 Final V2".

The second rejection risk is your own pipeline

The first time I re-ran a batch job, I double-posted eleven assets. Same files, submitted twice in one week. That is one of the fastest ways to get a contributor account reviewed, and I did it by accident because my batch script had no memory.

The fix is boring and it works: a ledger. upload_tracker.py is a small text-based record of what has been submitted. Three commands:

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

Every batch script I run now checks the ledger first and only processes what is pending. Re-runs became safe. This is also why it uses the standard library only. A tracker that needs a database server is a tracker you will not run on the box that actually does the work.

Why all of this runs on a 1 vCPU, 2 GB server

I run the whole pipeline on the cheapest server I could get: 1 vCPU, 2 GB RAM, no GPU. That constraint shaped the scripts more than anything else.

The habits that make it work:

  • One process at a time. Fanning out image jobs in parallel on a single core just thrashes.
  • gc.collect() after each asset, so memory does not creep across a 40-file batch.
  • Measure peak RSS with resource.getrusage(RUSAGE_SELF).ru_maxrss, not average usage. The peak is what gets you killed.
  • One file per subprocess for heavy render steps, so memory returns to the OS between items.
  • Inspect previews at 60 dpi, render finals at 300. Never render at final resolution just to check a layout.

The metadata and tracking scripts fit in this world trivially, because they move text, not pixels.

The rest of the packaging problem

Metadata was the first wall, but not the last. Two more things stalled me repeatedly:

Turning a written document into something that looks like a product. I had Markdown content and needed a PDF with real headings, tables, and links. md2pdf.py does that in one command, using reportlab. That is one of the only two third-party packages in the whole kit.

Bundling the product. A zip with a real cover image sells. A bare file does not. pack_product.py zips the files and renders a 1280×720 cover plus a 600×600 thumbnail. It writes into a _system folder by default, and you can redirect it with --dir or the PACK_ROOT environment variable.

The folder layout matters too. Mine is fixed: assets/ for raw output, output/ for submission-ready files plus their CSVs, packs/ for finished zips, research/ for notes and logs. Keep research/ forever. I deleted mine once to "clean up" and immediately regretted it when I needed my own notes for the next product.

What it added up to

The full pipeline, with these four scripts as the packaging layer, turned one prompt engine into 39 finished products, about 856 MB of output, running on that same 1 vCPU, 2 GB box. Around 19,000 lines of pipeline code total, with 300+ pages of research behind the rules the scripts enforce.

I am not going to pretend a metadata script is exciting. But rejections were the thing eating my time, and the fix was not more art, it was getting the boring layer right. I packaged the four scripts as the Pipeline Starter Kit, exactly the versions I run, with the install notes and the RAM habits included. If your bottleneck is the same one mine was, it will save you the afternoon I lost.

See the kit


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

Top comments (0)