DEV Community

Cover image for Two Packages, One Venv, Zero Excuses: Designing a Frictionless First Run**

Two Packages, One Venv, Zero Excuses: Designing a Frictionless First Run**

I still have the CSV that got rejected. Adobe's uploader bounced a batch of 12 images because my metadata header had six columns instead of five. Not "wrong values in column six". The whole file, refused on sight. I sat there at 11pm renaming columns by hand in a spreadsheet, and I remember thinking: the art took twenty minutes, the paperwork took two hours.

That was the moment I stopped treating packaging as an afterthought. Here is what actually works, learned the slow way.

The metadata header is a contract, not a suggestion

Each stock platform has its own exact CSV shape, and their uploaders check the header before they check anything else.

  • Adobe Stock: 5 columns
  • Vecteezy: 4 columns
  • Dreamstime: 15 columns

Get one column wrong and the file is rejected instantly, no partial upload, no helpful diff of what you missed. So the first script I wrote for myself, make_metadata.py, does one thing: it takes a folder of files and writes the exact header each platform demands, nothing else. It now emits all three formats from the same folder:

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

Without a titles file it title-cases the filename as a placeholder. That is fine for a first pass, but replace it before you submit. Reviewers notice lazy titles, and a rejection costs you days of review queue time.

Double-posting is the fastest way to get flagged

This one bit me early. I ran a batch upload twice because I lost track of what had already gone through. Duplicate submissions are exactly the kind of thing that gets an account flagged, and one flagged account is your whole distribution channel gone.

The fix is dumb and it works: a ledger. upload_tracker.py remembers what you have posted, so a re-run can never double-post.

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

Two of the four scripts in my kit, this one and make_metadata.py, use only the Python standard library. No pip install at all. If you only need metadata and tracking, you can run them on a fresh box in thirty seconds.

The first run should take one command, not one evening

The whole reason I care about frictionless setup is that I watched myself stall on my own tools. Every dependency I added was a future me, six months later, hitting a version conflict and abandoning the run. So the rule now: two third-party packages maximum, and only where they earn their place.

  • Pillow, for rendering product covers in pack_product.py
  • reportlab, for the PDF output in md2pdf.py

Everything else is stdlib. Install is:

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

One venv, two packages, done. md2pdf.py turns a Markdown file into a styled PDF in one command, headings, tables, lists, links intact. pack_product.py takes a folder and writes the zip, a 1280×720 cover, and a 600×600 thumbnail. A zip with a real cover sells. A bare file does not. I learned that from watching my own listing sit at zero until I added covers, and then watching the first sale come in at $0.99, which is not a number that changes your life but is absolutely a number that changes your week.

Small server habits that saved the pipeline

The entire thing ran on 1 vCPU, 2 GB RAM, no GPU. That constraint forced habits I would recommend even on big machines:

  • One process at a time. Never fan out image jobs in parallel on a 1-core box.
  • Call gc.collect() after each asset to free memory between items.
  • Measure peak RSS, not average. resource.getrusage(RUSAGE_SELF).ru_maxrss tells you the real ceiling.
  • One file per subprocess for heavy render steps, so memory returns to the OS.
  • Render at the resolution you need. Inspecting a preview at 60 dpi beats rendering it at 300.

And one non-technical habit: keep your research folder forever. My scrape logs and notes are the raw material for future products. I deleted them once to "clean up" and regretted it within a month.

Where this went

The scripts that came out of that rejected-CSV night have now packaged 39 finished products, 856 MB of output, sitting inside a parent project of about 19,000 lines of pipeline code, backed by 300+ pages of research. Of the art made with the wider system, 58 pieces are live on Adobe Stock today.

I have bundled the four scripts, the folder layout, and the tiny-server notes into a starter kit. It is the packaging layer, not a magic machine. It will not make the art for you and it will not get you sales on its own. What it does is remove the boring failure modes: wrong headers, double posts, missing covers, so the time you have goes into the work that matters.

If that sounds like your bottleneck, it is here:

Get digital asset factory


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

Top comments (0)