DEV Community

Cover image for How make_metadata.py Builds Adobe, Vecteezy and Dreamstime CSVs From One Asset Folder

How make_metadata.py Builds Adobe, Vecteezy and Dreamstime CSVs From One Asset Folder

Last March I lost an entire upload day to a CSV header. I had 40 JPEGs ready for Adobe Stock, I built the metadata file by hand in a spreadsheet, and the uploader rejected it in about two seconds. The reason was column order. Adobe wants five columns in an exact order, and I had a sixth column in there because I thought more data was better. It is not. The uploader does not warn you, it just fails, and you find out after the upload attempt.

That was the moment I stopped hand-building metadata and wrote make_metadata.py. This post is about how it works and what I learned about the three platforms it targets.

Each platform has its own fixed header, and you cannot improvise

The script writes three different CSV shapes, and none of them are negotiable:

  • Adobe Stock uses a 5-column header. The uploader checks the header row first. If the column names or order do not match what it expects, the whole batch fails before a single file is evaluated.
  • Vecteezy uses a 4-column header. Fewer columns, but the same rule applies. Wrong header, instant rejection.
  • Dreamstime uses a 15-column header. This is the widest of the three, and it is where hand-editing a spreadsheet really breaks down, because you are filling fifteen cells per asset and one misplaced category code can get the file bounced at review instead of at upload.

The script's job is narrow on purpose. It looks at a folder of files, filters by extension, and writes the exact header the platform demands, then one row per file. No extra columns, no "helpful" additions. When I say the header is exact, I mean the script was written against the headers that actually pass, and it writes nothing else.

Titles and keywords: a first pass is fine, a filename is not

By default, if you run:

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

the script takes each filename, title-cases it, and uses that as the title. For a first pass this is fine. It gets you a valid CSV you can inspect and submit.

But a filename like asset-001.jpg turned into "Asset 001" is a weak title, and reviewers notice. That is why the script also accepts --titles titles.json, a small JSON file where you supply real titles and keyword sets per asset. My honest workflow: generate the CSV with defaults, open it, and replace the lazy titles before submitting. The script gets you to a valid file fast, the last mile of quality is still yours. I would rather say that plainly than pretend the script writes good copy for you.

One asset folder, three outputs, zero rework

The point of the single-folder design is that the same ./my-assets directory feeds all three platforms:

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

Same files, three CSVs, each with the correct header. When I packaged 39 products this way, the metadata step stopped being a bottleneck entirely. It went from an afternoon of spreadsheet work per batch to three commands I could run while the next batch rendered.

The script itself uses only the Python standard library. No pip install, no dependencies to break. On the 1 vCPU, 2 GB RAM server this pipeline runs on, that matters more than it sounds. A metadata builder that pulls in pandas and a scraping framework is overkill for writing rows to a CSV, and every dependency is memory you do not have on a small box.

The failure mode I did not anticipate: running it twice

Once the CSV builder worked, I created a new problem for myself. I ran a batch, got distracted, and ran it again. Double-posted assets are the fastest way I know to get an account flagged, and platforms do not care that it was an accident.

That is why the kit pairs make_metadata.py with upload_tracker.py, a small ledger that also uses the standard library alone:

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 I submit anything now, pending shows me exactly what has not been posted. A re-run cannot double-post because the ledger remembers. It is a boring script, and boring is the point.

What I would tell someone starting this today

Three things, all learned by breaking them first:

  1. Do not hand-edit metadata CSVs. The headers are fixed and unforgiving. Adobe is 5 columns, Vecteezy is 4, Dreamstime is 15. Write the header once in code and never touch a spreadsheet again.
  2. Automate the structure, not the judgment. The script builds valid files. You still write the titles and keywords that pass review. Plan for that last mile.
  3. Track every submission. A ledger costs ten minutes to build and saves you from the one mistake that can cost the account.

None of this is clever. It is the unglamorous layer between making the art and having it listed, and it is the layer that stalled me the longest.

Where this lives now

make_metadata.py and upload_tracker.py are two of the four scripts in the Pipeline Starter Kit, the same ones that packaged those 39 products (856 MB of output) on that 1 vCPU, 2 GB server. The other two, md2pdf.py and pack_product.py, handle turning Markdown into a styled PDF and bundling a product with a 1280x720 cover and 600x600 thumbnail. The whole kit is four standalone scripts, two third-party packages at most, and no cloud account. It is the tool I built because I kept failing at this by hand, and I keep it honest for that reason.

See the kit


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

Top comments (0)