DEV Community

Cover image for The Four Scripts That Packaged 39 Stock Assets On A 2 GB Server

The Four Scripts That Packaged 39 Stock Assets On A 2 GB Server

I still have the screenshot. It's Adobe Stock's upload screen showing "Invalid CSV header" in red, next to a file I'd spent an hour building by hand. I had 12 assets ready, titles written, keywords picked, and the whole batch bounced because I had five columns in the wrong order. That was the moment I stopped treating metadata as an afterthought and started treating it as code.

Since then, the pipeline packaged 39 finished products, 856 MB of output, all on a 1 vCPU, 2 GB RAM server with no GPU. Here is what actually worked, and why.

The metadata problem is a header problem

All three big channels reject a wrong CSV header instantly. Adobe's upload format is five columns. Vecteezy is four. Dreamstime wants fifteen. If your first row does not match exactly, nothing else about your file matters, the review never even sees your keywords.

So the fix was boring and total: one script, make_metadata.py, that walks a folder of files and writes the exact header each platform demands, and nothing else. It runs on the standard library alone, no pip install, no API keys. I point it at ./my-assets, pass --platform adobe or --platform vecteezy or --platform dreamstime, and get a CSV I can upload directly.

One honest caveat: by default it title-cases the filename and uses that as the title. That is fine for a first pass and terrible for a final submission. The script accepts --titles titles.json so you can supply real titles and keywords. Replace the defaults before you hit submit, every time.

Double-posting is the fastest way to get flagged

The second failure I remember clearly: re-running a batch script at 1am, forgetting the first run had half-finished, and posting the same 20 files twice. Duplicate submissions are the kind of thing that gets an account flagged, and flagged accounts do not argue.

The fix is upload_tracker.py, a tiny ledger. pending items.txt shows what has not been posted yet. done "asset-001.jpg" marks it. status gives the overview. It is also standard library only, which matters because the two scripts I run most often should have zero setup friction. A re-run now physically cannot double-post, because the tracker is the source of truth, not my memory at 1am.

Packaging is what makes a file a product

A zip with a real cover sells. A bare file does not. I learned this by shipping something early that was just a folder of files, and watching it do nothing.

Two scripts handle this now. md2pdf.py turns a Markdown file into a styled PDF with headings, tables, lists and links, one command: python md2pdf.py content.md product.pdf "My Product Title". It uses reportlab, which is one of only two third-party packages in the whole kit.

pack_product.py bundles the rest: it zips the files and renders a cover at 1280x720 and a thumbnail at 600x600, using Pillow, the second package. The output lands in a _system folder, and you can redirect it with --dir or a PACK_ROOT environment variable instead of editing the script. I render covers at exactly the resolution I need and nothing larger, which matters more than you would think on the hardware I run.

The 2 GB server rules

Everything above ran on 1 vCPU and 2 GB of RAM. That constraint shaped the whole pipeline, and the habits are worth stealing even if you have better hardware:

  • One process at a time. Never fan out image jobs in parallel on a 1-core box, you just thrash.
  • Call gc.collect() after each asset. Free memory between items or the next one inherits the garbage.
  • Measure peak RSS, not average. resource.getrusage(RUSAGE_SELF).ru_maxrss tells you the real ceiling you have to stay under.
  • Run heavy render steps as one file per subprocess, so the OS actually gets the memory back.
  • Inspect previews at 60 dpi instead of rendering at 300. Render at the resolution you need, and only then.

None of this is clever. It is just what happens when the box is small and you cannot throw money at it.

The folder layout that scales

The last thing that mattered was structure. A catalogue turns into a junk drawer without a fixed one, so the kit assumes this:

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

One rule I paid for: keep research/ forever. Your scrape logs and notes are the raw material for future products. I deleted mine once to "clean up" and regretted it within a month.

Where this came from

These four scripts are the packaging layer of a bigger pipeline: about 19,000 lines of pipeline code in the parent project, backed by over 300 pages of research behind the rules the scripts enforce. The scripts themselves are what actually shipped the 39 products, so I pulled them out, stripped them to two dependencies, and documented the RAM notes and platform header rules they encode. That is the Pipeline Starter Kit. If any of this matches the bottleneck you are stuck at, it is the tool built from this exact work, no more, no less.

See the kit


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

Top comments (0)