One Set of Assets, Three Ways to Sell: Packaging Scripts Automate Stock Uploads, Product Packs, and Listing Renders

An independent creator uses four Python scripts to package the same asset into three sellable forms, avoiding manual errors and account risks.
Addressing the pain of manually packaging assets, the article proposes a "packaging layer" that wraps the same body of work into three forms: stock uploads, product packs, and listing renders. The core solution is four scripts relying only on the Python standard library and two third-party packages (Pillow, reportlab): make_metadata.py generates platform-correct CSV headers, upload_tracker.py records uploaded items to prevent duplicate submissions that trigger bans, pack_product.py generates a zip, a 1280x720 cover, and a 600x600 thumbnail in one command, and md2pdf.py converts Markdown into PDF products. The author also shares habits for running a 39-product, 856 MB pipeline on a small server with 1 vCPU, 2 GB of memory, and no GPU, including single-process execution, per-asset gc.collect(), measuring peak rather than average memory, and splitting re-render tasks into subprocesses. The ultimate lesson: an asset's sellability is determined by the packaging layer, and the packaging layer is best suited to scripting.
Why One Asset Should Be Sold Three Times
The pain of manually packaging assets is very concrete: you've finished 30 assets, but you're stuck on compression, screenshots, and filling out CSVs—one wrong column count and the platform rejects you instantly. The author's experience is a microcosm of many independent creators—the creative ability isn't lacking; what's missing is the "packaging layer" that turns the same body of work into three sellable forms.

The same batch of work can be split into three product forms, each with completely different packaging requirements:
- Stock uploads: individual files plus metadata CSVs in the platform's specified format. Adobe Stock requires a 5-column header, Vecteezy requires 4 columns, Dreamstime requires 15. Get the header wrong and the reviewer won't even see the work before rejecting it. The author had an entire batch scrapped because they wrote 6 columns for the Adobe template.
- Product packs: compress related files into a single zip and sell the whole pack on Gumroad or your own site. This requires a cover image that conveys the contents at a glance.
- Listing renders: a 1280x720 cover and a 600x600 thumbnail. These are the two images buyers actually see. The author's first listing had no preview images at all and ended with zero sales.
All three forms share the same asset folder. Treating these three things as separate manual tasks is the real time sink.
Metadata CSV is a spreadsheet file used by stock platforms for bulk uploads, where each row corresponds to one asset and columns contain fields like filename, title, keywords, and category. Different platforms have strict rules about the number, order, and header names of columns, because the uploader parses data directly by column index. For example, Adobe Stock requires 5 columns (filename, title, keywords, category, release info), Vecteezy requires 4, and Dreamstime has as many as 15. Once the header doesn't match, the system can't locate the fields and rejects the entire batch before human review. Therefore, metadata generation is essentially a format-adaptation problem, not a copywriting problem—outputting per-platform templates via script is the only reliable solution.
Metadata Is a Format Problem, Not a Copy Problem
Platforms leave no room for negotiation on CSV headers. Adobe's uploader checks the header row and rejects immediately if the format is wrong. The author's solution was to write a script called make_metadata.py that emits only the header and does nothing extra:
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
The script walks the folder and converts filenames to title case before writing rows. If you have a real titles.json on hand, you can pass in titles and keywords; if not, a first pass with filenames will do, but be sure to replace them before submitting. This script uses only the Python standard library, installs no dependencies, and takes one command per platform—the header can never be wrong again.
Duplicate Uploads Are the Fastest Way to Get Banned
The author particularly emphasizes this point: resubmitting the same batch of assets to the same platform is a known signal that triggers account review. Once you automate, reruns aren't a matter of "if" but "when"—a scheduled task runs an extra time, or you forget a batch was already published—these are common scenarios.
upload_tracker.py is a tiny ledger:
python upload_tracker.py pending items.txt
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
It remembers what has already been published and automatically skips completed items on reruns. Also standard-library only, about a hundred lines of code—the most unremarkable component in the entire pipeline, but the one that prevents the most losses.
Account review is part of the risk-control mechanism on stock platforms. When the system detects that the same asset has been submitted multiple times, or that upload behavior shows machine-like patterns (such as fixed intervals or large batches of duplicates), it automatically flags the account and escalates to manual review. During review, the account's upload and earnings functions may be frozen, and in severe cases the account may be banned. Duplicate uploads are dangerous because they are easily misjudged as "malicious volume boosting" or "review evasion"—even when the creator has no such intent. If an automation script lacks idempotency (i.e., repeated execution produces no additional side effects), a rerun of a scheduled task or a loss of state will directly trigger this mechanism. Therefore, a ledger recording uploaded items is not an optional optimization but infrastructure for avoiding account loss.
One Command to Generate Covers and Zip Files
pack_product.py handles product packaging and image rendering:
python pack_product.py my-product "My Product" "A short subtitle" 9
This command generates my-product.zip, a 1280x720 cover, and a 600x600 thumbnail in the _system folder of the current directory. You can change the output location with --dir or the PACK_ROOT environment variable. Rendering depends on Pillow, one of only two third-party packages in the entire toolkit; the other is reportlab, which md2pdf.py uses to convert Markdown documents into styled PDF products with one command.
Why 1280x720 and 600x600 specifically? Because these two sizes look just right in the listing preview area and thumbnail grid, without wasting rendering time. Rendering at the resolution you actually need is one of the key habits that lets the entire pipeline run on a small server with 1 vCPU, 2 GB of memory, and no GPU.
A Few Habits for Running the Whole Pipeline on a Small Server
The complete pipeline packaged 39 products and produced a total of 856 MB, all running on that small machine. It wasn't hardware that made it work—it was a few concrete habits:
- Run only one process at a time. Never parallelize image tasks on a single core.
- Call
gc.collect()after each asset is processed to free memory between items. - Measure peak memory with
resource.getrusage(RUSAGE_SELF).ru_maxrss, not average memory—averages lie about your memory ceiling. - Split re-render steps into one subprocess per file so memory is truly returned to the OS.
None of this is clever—it just lets an independent creator run a real pipeline on nearly zero-cost hardware.
gc.collect() is a forced garbage collection function provided by Python's standard library gc module. Python mainly manages memory through reference counting, but scenarios like circular references require the generational garbage collector to step in. When batch-processing large objects such as images, memory may not be returned to the OS immediately even after objects are no longer in use, causing peak memory to keep climbing. Calling gc.collect() per asset can proactively trigger collection and promptly release image buffers that are no longer referenced, allowing a small machine with 1 vCPU and 2 GB of memory to run the entire pipeline stably. Paired with resource.getrusage(RUSAGE_SELF).ru_maxrss, which reads the process's peak resident set size (RSS), it helps developers understand the real memory ceiling instead of being misled by averages.
Where the Toolkit Stands
The above has been organized into four standalone scripts: make_metadata.py, upload_tracker.py, md2pdf.py, and pack_product.py. No framework, no cloud account needed, just two pip packages. They come from a larger project—about 19,000 lines of pipeline code and 300+ pages of research supporting its rules—now offered separately as the "AI Sticker Factory Handbook." In the author's own words: if you don't want to start rebuilding the entire packaging layer from a rejected CSV at midnight, this is already here.
For anyone making digital products, the lesson here isn't limited to stickers: the "sellability" of assets is largely determined by the packaging layer, and the packaging layer is precisely the part best suited to scripting.
Related articles

WorkBuddy Step-by-Step Tutorial: Get an Automated Workflow Running in Half an Hour
A step-by-step breakdown of the WorkBuddy automated workflow: why most people only use it as a chat app, how the 27-anchor course and 12 templates help beginners get up and running in half an hour, plus notes on usage boundaries.

The Truth About Free Claude Code: Three Legitimate Paths and One Scam
Is free Claude Code possible? We break down the real cost of Pro and Max (with exchange rates and taxes), how community free methods work and their privacy trade-offs, plus fake installers and phishing traps—and three legitimate free paths.

Codex Cloud Mode Explained: Environment Setup and Use Cases
This article explains how Codex cloud mode works, environment setup steps, and use cases, including how to create environments, connect GitHub repositories, set scheduled tasks, and compare cloud vs. local mode.