DEV Community

Cover image for Your Folder Structure Is Writing Your Metadata (Whether You Meant It To)**

Your Folder Structure Is Writing Your Metadata (Whether You Meant It To)**

First batch I ran through my own metadata script, I left off the titles file. Forty files. The script did exactly what I wrote it to do: it title-cased each filename and put that in the title column. So my upload CSV had forty rows of Asset 001.Jpg, Asset 002.Jpg, and so on. The importer took it. The review queue did not. That was the day I understood the folder is not storage. The folder is the first draft of the metadata, and if you leave it alone, it submits itself.

Adobe wants 5 columns. Vecteezy wants 4. Dreamstime wants 15.

All three channels validate the header line before they read a single row. Get it wrong and the file is rejected instantly, no partial import, no "we skipped the bad rows." Header is the gate.

Adobe takes 5 columns. Vecteezy takes 4. Dreamstime takes 15. Those counts are fixed and they do not overlap. My make_metadata.py has one function per channel that writes the header and nothing else. No guessing, no extra columns, no "just in case" field.

This is why hand-building a CSV in a spreadsheet breaks at scale. You maintain three different column layouts by hand and sooner or later you paste Vecteezy columns into an Adobe upload. The script has no such day.

The folder names your titles whether you ask or not

My scripts assume this layout:

project/
  assets/            raw output, one file per asset
  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

Every asset in assets/ is a row. The filename is the fallback title. That fallback is fine for a first pass, and it is exactly what burned me, because "fine for a first pass" and "fine to submit" are different sentences. Pass --titles titles.json and the filename never reaches the CSV.

The rule I settled on: a filename is a primary key, not a product name. asset-001.jpg is how the ledger finds the row. It should never appear in the title column of something a human reviews.

research/ stays forever. I deleted one round of scrape logs to tidy up, then needed them three weeks later for a second product. Your old notes are raw material. The tidy version costs you the future product.

The ledger is the part that saves the account

Fastest way to get an account flagged is running your batch twice. Not a wrong keyword, not a weak description. Submitting the same file twice.

upload_tracker.py is a ledger. It stores what is done and answers a smaller question:

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

pending is the only list I ever feed to the next upload. If I re-run the whole job after a crash at item 30, pending returns items 31 onward. Re-running becomes safe. That is the entire design. A pile of text, no database.

1 vCPU, 2 GB RAM, no GPU

Whole pipeline ran on one core and two gigs. It works because of habits, not hardware.

One process at a time. Never fan out image jobs in parallel on a one core box, you just swap until the OOM killer ends the conversation.

Call gc.collect() after each asset. Boring, and it keeps the peak down.

Measure peak RSS, not average. resource.getrusage(RUSAGE_SELF).ru_maxrss gives you the real ceiling. Average memory usage on a pipeline that spikes once per file tells you nothing useful.

One file per subprocess for heavy render steps. Memory goes back to the OS when the child exits, which is the only reliable reset without a restart.

Render at the resolution you need. Inspecting a preview at 60 dpi beats rendering it at 300 and then throwing it away.

None of this is clever. It is the difference between a job that finishes and a job that dies at item 212.

Where this left me

Not a big catalogue. 1,700 assets made. 58 live on Adobe. First sale was $0.99. That first sale took a while, and the 58 came after real rejections on title and keyword quality, all of them traceable back to the placeholder pass I described at the top.

The scripts shipped 39 finished products, 856 MB of output. The parent pipeline is about 19,000 lines. The rules the scripts enforce came out of 300+ pages of research on what each channel actually validates, how reviewers read a title, and where the importer stops reading.

The pattern is the same in all of it: the structure you keep is the format you ship. Folder, filename, header column count, tracker state. Set them once and the boring parts stop being decisions.

I put the four scripts (make_metadata.py, upload_tracker.py, md2pdf.py, pack_product.py) together as the Digital Asset Factory, the full system this work came from. It is the packaging layer plus the folder conventions that keep the metadata honest, and it runs on the same tiny box. If you already have a folder of files and no clean CSV, that is where it saves you time. If you already have a system you like, you probably do not need it.

Get digital asset factory


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

Top comments (0)