Two Lines in Requirements.txt: Pillow, Reportlab, and Nothing Else
Two Lines in Requirements.txt: Pillow, Reportlab, and Nothing Else
Requirements.txt 中的两行代码:Pillow、Reportlab,仅此而已
The rejection email was four words long: “Invalid CSV header.” I had just spent three hours naming 60 vector files and writing titles. All of it was sitting in a spreadsheet I built by hand, and Vecteezy’s uploader refused the whole batch because one column was in the wrong place. No partial credit. No “fix it and retry.” Just reject.
拒收邮件只有四个字:“CSV 标题无效”。我刚刚花了三个小时为 60 个矢量文件命名并撰写标题。所有这些都放在我手工制作的电子表格中,但 Vecteezy 的上传器拒绝了整批文件,仅仅因为其中一列放错了位置。没有部分评分,没有“修改后重试”,只有拒绝。
That was the moment I stopped treating packaging as something I would do “later, carefully.” It turns out “carefully” is not a schedule. So I wrote the scripts. They shipped 39 products out the door, and the entire dependency list is this: pillow reportlab Two lines. The rest is the Python standard library. Here is what actually works and why.
那一刻,我不再把打包工作视为“以后再仔细做”的事情。事实证明,“仔细”并不是一个时间表。所以我编写了脚本。它们成功发布了 39 个产品,而整个依赖列表只有这些:pillow 和 reportlab,仅两行。其余部分均使用 Python 标准库。以下是真正有效的方法及其原因。
Wrong headers get rejected instantly, so generate them. Adobe, Vecteezy and Dreamstime each want a different CSV. Adobe takes 5 columns, Vecteezy takes 4, and Dreamstime takes 15. Get one header text wrong and the whole upload dies before a human ever looks at your art. So make_metadata.py does one job: it walks a folder of files and writes the exact header each platform demands, nothing more.
错误的标题会被立即拒绝,所以要自动生成它们。Adobe、Vecteezy 和 Dreamstime 各自需要不同的 CSV 格式。Adobe 需要 5 列,Vecteezy 需要 4 列,而 Dreamstime 需要 15 列。只要错一个标题文本,在人工审核你的作品之前,整个上传过程就会失败。因此,make_metadata.py 只做一件事:遍历文件文件夹,并写入每个平台要求的精确标题,不多不少。
Three runs, three files: 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
运行三次,生成三个文件: 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
Without extra input, the filename is title-cased into the title. That is fine for a first pass, but replace it before you submit. The script accepts —titles titles.json when you have real titles and keywords ready. This script needs no install. Standard library only. If metadata is the thing you keep putting off, you can fix it today with zero packages.
如果没有额外输入,文件名会被转换为标题格式作为标题。这对于初次尝试还可以,但在提交前请务必替换它。当你准备好真实的标题和关键词时,脚本支持 —titles titles.json 参数。此脚本无需安装,仅使用标准库。如果元数据是你一直拖延的事情,你今天就可以在零依赖包的情况下解决它。
Double posting is how accounts get flagged. The second failure mode is sneakier. You run a batch halfway, something interrupts you, you run it again, and suddenly 40 of your 60 assets are live twice. Running a batch twice is the fastest way to get an account flagged. upload_tracker.py is a plain ledger. It remembers what you have marked done, so a re-run only shows what is left:
重复发布会导致账户被标记。第二种失败模式更隐蔽。你运行了一半批处理,中途被打断,再次运行后,突然间 60 个资产中有 40 个被发布了两次。运行两次批处理是导致账户被标记的最快方式。upload_tracker.py 是一个简单的账本。它会记住你标记为“完成”的内容,因此重新运行时只会显示剩余未完成的部分:
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
python upload_tracker.py pending items.txt # 查看尚未完成的内容 python upload_tracker.py done “asset-001.jpg” python upload_tracker.py status
No database, no cloud account, no sync service. A file plus a script. It has never failed me because there is nothing in it to fail.
没有数据库,没有云账户,没有同步服务。一个文件加一个脚本。它从未让我失望,因为它简单到没有任何出错的可能。
A product needs a cover, and a PDF needs a renderer. A zip with a real cover sells. A bare file does not. That is the only reason Pillow is in the requirements. pack_product.py zips the files and renders a 1280x720 cover plus a 600x600 thumbnail: python pack_product.py my-product “My Product” “A short subtitle” 9
产品需要封面,PDF 需要渲染器。带有精美封面的压缩包才卖得出去,光秃秃的文件则不然。这就是 Pillow 出现在依赖项中的唯一原因。pack_product.py 会压缩文件并渲染出 1280x720 的封面以及 600x600 的缩略图: python pack_product.py my-product “My Product” “A short subtitle” 9
The other package, reportlab, exists because a PDF with real headings, tables, lists and links makes a Markdown doc look like a product instead of a draft: python md2pdf.py content.md product.pdf “My Product Title”
另一个包 reportlab 的存在,是因为带有真实标题、表格、列表和链接的 PDF 能让 Markdown 文档看起来像个成品,而不是草稿: python md2pdf.py content.md product.pdf “My Product Title”
Those are the only two third-party packages. Everything else, the CSV generation, the tracking, the zip, runs on Python alone. The whole pipeline ran on 1 vCPU and 2 GB of RAM. This is the part people find hard to believe. The pipeline that produced 39 products and 856 MB of output ran on 1 vCPU, 2 GB of RAM and no GPU.
这就是仅有的两个第三方包。其他所有功能,包括 CSV 生成、追踪和压缩,都仅靠 Python 运行。整个流水线在 1 个 vCPU 和 2 GB 内存上运行。这是人们最难以置信的部分。这个产出了 39 个产品和 856 MB 输出文件的流水线,是在 1 个 vCPU、2 GB 内存且没有 GPU 的环境下运行的。
It held together because of a few unglamorous habits: One process at a time. Never fan out image jobs in parallel on one core. 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. Run heavy render steps as one file per subprocess, so memory goes back to the OS. Render at the resolution you need. A 60 dpi preview beats a 300 dpi one when you are just checking.
它之所以能稳定运行,是因为遵循了几个不起眼的习惯:一次只处理一个进程。永远不要在单核上并行处理图像任务。在处理完每个资产后调用 gc.collect() 以释放内存。测量峰值 RSS 而非平均值。resource.getrusage(RUSAGE_SELF).ru_maxrss 能告诉你真实的内存上限。将繁重的渲染步骤作为每个子进程处理一个文件,这样内存就能归还给操作系统。按需渲染分辨率。当你只是检查时,60 dpi 的预览图比 300 dpi 的更好。
The parent project has roughly 19,000 lines of pipeline code and 300+ pages of research behind the rules these scripts enforce. The scripts themselves are small. The point is that small is enough.
父项目大约有 19,000 行流水线代码,这些脚本所执行的规则背后有 300 多页的研究支撑。脚本本身很小。重点在于,小就足够了。
The folder layout that keeps it from turning into a junk drawer: project/ assets/ raw output (one file per asset) output/ ready-to-submit files + metadata.csv packs/ finished product zips + covers research/ your notes and sources scripts/ these tools
防止项目变成杂物堆的文件夹布局: project/ assets/ raw output (每个资产一个文件) output/ 待提交文件 + metadata.csv packs/ 成品压缩包 + 封面 research/ 你的笔记和来源 scripts/ 这些工具
Keep research/ forever. Scrape logs and notes are the raw material for the next product. Deleting them to “clean up” is a mistake I made once and will not repeat. These four scripts sit at the packaging layer. Above them, the full pipeline runs on a free stack: Hermes Agent schedules jobs, retries failures and makes decisions, with 9Router handling local LLM access.
永远保留 research/ 文件夹。抓取日志和笔记是下一个产品的原材料。为了“清理”而删除它们是我曾经犯过一次且绝不会再犯的错误。这四个脚本位于打包层。在它们之上,整个流水线运行在一个免费的技术栈上:Hermes Agent 负责调度任务、重试失败并做出决策,而 9Router 则处理本地 LLM 访问。
The packaging layer is where the rules from 300+ pages of research get enforced, and it is the part you can copy today. None of this is clever. The requirements file is two lines. The scripts are boring on purpose. That is exactly why they shipped 39 products instead of sitting in a backlog with the metadata CSVs I never got around to fixing.
打包层是执行 300 多页研究规则的地方,也是你今天就可以复制的部分。这一切并不高明。需求文件只有两行。脚本故意写得很枯燥。这正是它们能成功发布 39 个产品,而不是像我那些从未修复的元数据 CSV 一样堆积在待办事项中的原因。
If you are building vector packs, stickers or templates and the packaging keeps stalling you, this is the tool built from that work, the same scripts and the playbook behind them.
如果你正在制作矢量包、贴纸或模板,而打包工作总是让你停滞不前,这就是基于这些工作构建的工具,以及同样的脚本和背后的操作手册。