[KongchangAI]
· 3 min read· 1,650 words

Packaging 39 Assets on a 2GB Server: An Automation Pipeline Built from Four Scripts

Packaging 39 Assets on a 2GB Server: An Automation Pipeline Built from Four Scripts

A developer used four scripts to handle metadata, tracking, and packaging on a 2GB server, delivering 39 asset products.

The article tells how a developer, after an entire batch of assets was rejected by Adobe Stock due to a CSV header ordering error, redesigned his metadata workflow. Treating titles and keywords as code rather than after-the-fact patches, he used four Python scripts — make_metadata, upload_tracker, md2pdf, and pack_product — relying only on the standard library plus two third-party packages to complete metadata generation, upload tracking, PDF conversion, and product packaging. On a 1 vCPU, 2GB RAM server, he packaged 39 finished products and 856MB of output. The article highlights platform header differences, duplicate-submission ban risks, the value of product packaging, and survival rules for low-spec hardware.

A developer uploaded 12 assets to Adobe Stock, and because the five-column CSV header was in the wrong order, the entire batch was rejected by the system — the red text reading "Invalid CSV header" wiped out a whole hour of manual file assembly. That failure forced him to rethink his metadata workflow: stop treating titles and keywords as after-the-fact patches, and start managing them like code. In the end, he used four scripts to package 39 finished assets and 856MB of output on a server with 1 CPU core, 2GB of RAM, and no GPU.

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

The Metadata Problem Is Really a Header Problem

Adobe Stock, Vecteezy, and Dreamstime each have different upload formats. Adobe requires five columns, Vecteezy four, and Dreamstime as many as fifteen. If the first row of the CSV doesn't exactly match the platform's requirements, no amount of quality in the rest of the content will help — the review process simply won't see your keywords.

The solution is make_metadata.py: a script that walks a folder and generates headers precisely per platform. It relies only on the Python standard library — no pip install, no API key. Usage is simple: point it at your asset directory, pass --platform adobe, --platform vecteezy, or --platform dreamstime, and you get a CSV ready for upload.

The author candidly notes one limitation: by default the script converts filenames into title case and uses them as titles. That's good enough for a first pass, but never suitable for final submission. The script supports a --titles titles.json argument to supply real titles and keywords. Every submission requires replacing the defaults — no exceptions.

CSV (Comma-Separated Values) is a general-purpose format for storing tabular data as plain text, with one record per line and fields separated by commas. It has no strict standard, and different tools handle quoting, escaping, and line breaks inconsistently, making it highly prone to compatibility issues when exchanging data between programs. Asset platforms typically mandate a fixed number of columns and a fixed column order — for example, requiring title, description, keywords, category, and author to appear in a specific sequence on the cover sheet. Parsers generally read data by position rather than by column name, so even if the column names are correct, any misordering will still cause the system to flag it as an "Invalid CSV header" and reject the entire batch. This also explains why the developer insists that "the header problem is the essence": the header is the first gate of the entire upload process, and if the gate doesn't open, no amount of quality content can enter review.

Duplicate Submissions Are the Fastest Path to a Banned Account

The second lesson came at one in the morning: while rerunning a batch script, he forgot that the first run had already processed more than half, and the same asset got uploaded twice. Duplicate submissions count as high-risk behavior on asset platforms, and once an account is flagged, there is very little room for appeal.

upload_tracker.py is a minimalist ledger, also using only the standard library. pending items.txt lists assets not yet submitted, done "asset-001.jpg" marks one as complete, and status gives a global overview. The two most frequently used scripts have zero configuration friction, which matters enormously in real use. Because the tracker becomes the single source of truth, rerunning scripts physically cannot cause duplicate submissions — no more relying on one-in-the-morning memory.

Asset platforms typically determine duplicate submissions based on content fingerprints (such as file hashes) or metadata similarity. Once the same asset has been uploaded multiple times, the system may identify it as inflation or fraud, ranging from rejecting that entry to outright flagging the account. Such flags are often issued by automated risk-control systems, leaving a very small window for manual appeals, and the appellant must prove the action wasn't malicious — an extremely costly process. The value of an upload tracker lies in extracting the "already submitted or not" state from human memory or temporary files and solidifying it into a queryable ledger. When there is only one authoritative source of state, rerunning a script doesn't change the fact that something was already submitted, mechanically eliminating the risk of duplication rather than depending on the operator's caution.

Packaging Is the Critical Leap from File to Product

A zip package with a cover image sells; a bare file does not. Early on, the author delivered only a folder, and market feedback was zero.

md2pdf.py converts Markdown into a styled PDF, supporting headings, tables, lists, and links in a single command: python md2pdf.py content.md product.pdf "My Product Title". It depends on reportlab, one of only two third-party packages in the entire toolkit.

pack_product.py handles the rest of the packaging work: compressing files and using Pillow (the second third-party package) to generate a 1280x720 cover and a 600x600 thumbnail. Output defaults to a _system folder and can be redirected via --dir or the PACK_ROOT environment variable without modifying the script. The cover is only rendered at the required resolutions — not a single pixel more. On low-spec hardware, this habit matters more than you'd think.

On asset platforms, a "product" usually refers to a set of deliverables a buyer can use directly, commonly in the form of a zip archive containing a cover image and multiple files. Compared with a bare single file, a zip package has a cover thumbnail displayed on the platform's listing page, making it easier for browsers to click. Cover images generally need two sizes — 1280x720 and 600x600 — to fit different display slots. "Rendering only at the required resolution" means not generating a 300 dpi or larger original and then scaling it down, but drawing directly at the target size, thereby avoiding excessively high memory peaks caused by temporary large images on low-spec servers. On a machine with 2GB of RAM, this habit directly determines whether a task can finish at all — it's not merely an optimization trick.

Survival Rules for a 2GB Server

All of the above workloads run on a machine with 1 vCPU and 2GB of RAM. Resource constraints shaped the entire pipeline, and these habits are worth keeping even on better hardware:

  • Run only one process at a time. On a single-core machine, running image tasks in parallel only makes them compete for resources.
  • Call gc.collect() after processing each asset. Release memory between assets, or the next one inherits the previous round's garbage.
  • Watch peak RSS, not the average. resource.getrusage(RUSAGE_SELF).ru_maxrss tells you where the real memory ceiling is.
  • Run each re-render step as a separate subprocess. That way the operating system can actually reclaim memory.
  • Preview checks at 60 dpi, not 300 dpi. Render only the needed resolution and nothing else.

None of this is particularly clever — it's simply the natural choice when you can't throw money at the problem on a small machine.

RSS (Resident Set Size) refers to the amount of physical memory a process actually occupies, and unlike virtual memory, it reflects the portion truly resident in RAM. Averages mask short-lived memory spikes, and OOM (Out Of Memory) is usually triggered by those spikes, so monitoring peak RSS is more meaningful than monitoring the average. In Python, resource.getrusage(RUSAGE_SELF).ru_maxrss returns the peak resident memory of the current process since it started, in units that vary by system (kilobytes on Linux). gc.collect() forces garbage collection to release objects no longer referenced. Calling it alone won't solve all memory problems, because in CPython the operating system fully reclaims a subprocess's memory only after it exits — so splitting each re-render step into an independent subprocess effectively trades a process boundary for genuine memory return.

A Scalable Directory Structure

Without a fixed structure, an asset library quickly becomes a junk drawer. The toolkit assumes the following layout:

project/
  assets/            Raw output, one file per asset
  output/            Files pending submission + metadata.csv
  packs/             Finished zip + cover
  research/          Notes and sources
  scripts/           Tool scripts

The author learned one rule from one painful lesson: keep the research/ directory forever. Scrape logs and notes are raw material for future products. He once deleted it for the sake of "cleanup" and regretted it within a month.

The Full Picture Behind the Four Scripts

These four scripts are the packaging layer of a larger pipeline: the parent project is roughly 19,000 lines of pipeline code, with over 300 pages of research backing the rules. The scripts themselves delivered those 39 products, and the author extracted them, stripped them down to just two dependencies, and documented the memory notes and platform header rules into a Pipeline Starter Kit.

If you're stuck at the same bottleneck, this toolkit was built out of this specific work — nothing more, nothing less.

People building digital products can check out the free guide and toolkit from the author.

One-line Summary

A developer used four scripts to complete metadata, tracking, and packaging workflows on a 2GB server, delivering 39 asset products.

Paragraph Summary

The article tells the story of a developer who, after an entire batch of assets was rejected by Adobe Stock due to a CSV header ordering error, redesigned his metadata workflow. Treating titles and keywords as code rather than after-the-fact patches, he ultimately used four Python scripts — make_metadata, upload_tracker, md2pdf, and pack_product — relying only on the standard library plus two third-party packages to complete metadata generation, upload tracking, PDF conversion, and product packaging. On a server with 1 vCPU and 2GB of RAM, he packaged 39 finished products and 856MB of output. The article emphasizes platform header differences, the account-ban risk of duplicate submissions, the value of product packaging, and survival rules for low-spec hardware such as running a single process, reclaiming memory promptly, and monitoring peak RSS.

#Adobe Stock metadata#CSV header rules#Python automation pipeline#low-spec server memory management#asset packaging workflow
Share:

Related articles