Skip to content

Add bulk-csv-videos example - #12

Open
musab123-afk wants to merge 4 commits into
shotstack:masterfrom
musab123-afk:add-bulk-csv-videos-example
Open

Add bulk-csv-videos example#12
musab123-afk wants to merge 4 commits into
shotstack:masterfrom
musab123-afk:add-bulk-csv-videos-example

Conversation

@musab123-afk

Copy link
Copy Markdown
Contributor

One video per CSV row from a single reusable template: a validated 100-row dataset, a paced submit loop implemented in both Node.js and Python, a resumable manifest, and an optional AI data step where Claude writes each row's headline and image prompt. Companion code for https://shotstack.io/learn/bulk-create-videos-from-csv-and-ai/. Verified end-to-end on the sandbox in both languages, including the AI step; all failure paths print one actionable line and exit non-zero per STANDARDS.md, and prettier/ruff pass with the repo configs.

@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

@musab123-afk is attempting to deploy a commit to the dazzatron Team on Vercel.

A member of the Team first needs to authorize it.

@dazzatronus

Copy link
Copy Markdown
Contributor

#13 changed STANDARDS.md after you opened this. .env.example values, Python exception handling, check 5. Several came from reading this PR. Rebase for the new text.

Ran it end to end against stage: template created from template.json via the README command, SHOTSTACK_ROW_LIMIT=3, both languages. Three renders each, three hosted CDN URLs, status reported done for all of them. The happy path works.

Manifest design is good. Atomic writes. submitting to unknown recovery. 30s abort so an accepted render is never sent twice. Most people get that last one wrong.

Rule Status
CI (prettier + ruff) pass
README commands work, in sequence pass
name matches the directory pass
engines >= .nvmrc pass
format script pass
.env.example values empty fail (new in #13)
README says how to load .env fail (new in #13)
Missing API key prints one actionable line fail
No stack traces fail
Two-language files agree fail
Runs after a fresh clone fail
README: 5 sections, in order 6th section, ## Notes

1. .env.example blocks its own key check.

Ships SHOTSTACK_API_KEY=your_sandbox_api_key. README says copy it. if COMMAND != "summary" and not API_KEY tests falsiness only. The placeholder passes. Run dies on an API 401 instead of Set SHOTSTACK_API_KEY before running this command.

Blank every value. URL as a comment above. instagram-ai-video has the pattern. README needs the load instruction too — export, since this is two-language.

2. Two stack traces. Both reproduced.

bulk_render.py:28SHOTSTACK_REQUEST_INTERVAL_MS=abc. ValueError at module scope, outside the try/except. Node prints SHOTSTACK_REQUEST_INTERVAL_MS must be zero or greater.

bulk_render.py:328 — a stray comma gives DictReader a None key. json.dumps(..., sort_keys=True) raises TypeError: '<' not supported between instances of 'NoneType' and 'str'. Validation misses it — only named columns are read. Node: Invalid Record Length: columns length is 6, got 7 on line 2.

Third by inspection. bulk_render.py:276 and :449 check body is a dict, then assume body["response"] is one. {"response": "Invalid template id"} raises AttributeError. Line 74 already guards with isinstance(response, dict).

All three covered by catching Exception.

3. Switching languages poisons the manifest.

csv.DictReader doesn't trim. The Node parser sets trim: true. So the two hash different bytes for the same file.

Reproduced against the live API with this row:

row_id,product_name,headline,price,image_url,brand_color
hash-001, Widget , Nice headline ,$10,https://shotstack-assets.s3.amazonaws.com/images/waterfall-square.jpg,#0f766e

Node submits it and records the hash of the trimmed values. Python then reads the same file, unedited, and refuses:

Row hash-001 changed after its first submission. Use a new row_id, or retry it only after confirming the previous request failed.

Nobody touched the CSV. README.md:100 warns against running both submitters against one manifest — this is why. Worth fixing rather than documenting.

Separately, a row with an untrimmed brand_color fails Python validation outright (brand_color must be a six-digit hex color) and passes in Node.

4. Template ignores the AI image prompts.

template.json has one image asset and three rich-text. {{IMAGE_PROMPT}} appears nowhere. Step 4 pays Claude for 100 prompts. Videos come out identical to the non-AI run. Both READMEs lead with the feature. The caveat sits at README.md:81.

Add a text-to-image asset, or drop the prompt generation.

5. Committed artifact.

__pycache__/bulk_render.cpython-313.pyc. __pycache__/ missing from .gitignore.

6. Two README claims are wrong.

README.md:87 — "status polls each render until it is done or failed". update_statuses is one pass over manifest["rows"]. One lookup per render. Then exit. Re-run it until the counts settle.

README.md:15 — "Node.js 20 or later, or Python 3". Node is required on both paths. generate-data.mjs is the only thing producing products.csv. Setup opens with npm install.

7. Thin token margin. Not a blocker. Untested.

generate-data-ai.mjs:85max_tokens: 16000, no thinking parameter. claude-opus-5 thinks by default. Thinking shares the budget with response text. 100 rows is roughly 11k response tokens before thinking. I had no Anthropic key, so this is arithmetic, not a measurement.

You handle the outcome — stop_reason === 'max_tokens' prints "Raise max_tokens or send fewer rows". Margin, not breakage. Raise max_tokens, batch rows, or thinking: {type: 'disabled'} at effort: 'low'.

generate-data-ai.mjs:111 prints the raw Anthropic body. Exits clean. A 401 shows several lines of JSON, never names ANTHROPIC_API_KEY. bulk-render.mjs gets this right.

Review load

1,635 lines in one pass. Seams: Node pipeline, Python port, AI generator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants