Vibecode Reel Farm
track this build4 phases, 12 steps, beginner friendly0%The pipeline here is not a secret: an LLM writes a short script, a TTS API reads it, stock or generated clips get stitched behind it, and word-level timings drive burned-in captions. ffmpeg does the heavy lifting and an agent can wire the whole chain in a weekend, including a queue that renders fifty variations overnight. Where it stops being fun is everything after the render: scheduled posting to TikTok, Instagram and YouTube means real API access, app review, tokens that expire, and platform rules that change without warning. You will also spend more time than you expect on the boring parts, safe-area layout for captions, loudness normalization, and clips that do not visually repeat every third video. Build it if you want control over the script and the look, pay if the value you actually want is the post button.
You are building a lean indie version of Reel Farm. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Reel Farm · indie build A pipeline turning a topic into short-form video posts, where the interesting engineering is the render pipeline and caption timing, and the design constraints are that every output is labelled as synthetic and the posting rate is bounded by a constant you cannot raise at runtime. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Sourcing | Content from sources you record with a licence | images and clips in a published video are someone's work | | Rendering | ffmpeg with a declarative scene description | reproducible renders matter more than a flexible editor | | Captions | Timings derived from synthesised speech, never estimated | captions estimated from reading speed drift within a minute | | Marking | Provenance metadata embedded at render, plus platform disclosure on upload | the obligation is machine readable marking and human disclosure, and they are different things | | Publishing | A rate-limited queue against official APIs only | automation outside the official API is what gets accounts removed | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Python 3.12 or newer** · free - Why: Orchestrates generation, rendering and publishing. - Get it: python.org or your package manager, then a virtual environment. - Verify: python3 --version prints 3.12 or higher - [ ] **ffmpeg with the codecs the platforms accept** · free - Why: Rendering, and each platform has its own container, codec and aspect requirements. - Get it: Your package manager, then check each target platform's published encoding requirements. - Verify: A rendered file uploads to your target platform without re-encoding - [ ] **The disclosure rules that apply to you** · free to read - Why: The EU transparency obligations have applied since 2 August 2026 with marking requirements following, and every platform has its own rule. The build enforces both, so you need to know what both say. - Get it: Read the transparency obligations for your jurisdiction and the synthetic content policy of every platform you will post to. Write down what each requires. - Verify: A written note states the marking and disclosure requirements per jurisdiction and platform - [ ] **Official API access for one platform** · free, but approval takes time - Why: Publishing through anything other than the official API violates the terms and removes accounts, and API access often needs review before it is granted. - Get it: Apply for developer access on one platform and read its rate limits and synthetic content requirements before building the publish path. - Verify: A test post publishes through the official API with the disclosure flag set - [ ] **A licensed asset library** · free for your own work; a subscription for a stock library - Why: Volume turns one unlicensed image into hundreds of infringing posts, and the licence record is required before an asset can be used. - Get it: Assets you created, or a library whose licence explicitly permits commercial derivative use. Save the licence text with them. - Verify: Twenty assets exist, each with a licence record permitting your use ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Publishing unlabelled synthetic content. - Automation outside official APIs. - Using a real person's likeness or voice. - Volume beyond the cap. - Scheduled auto-posting to TikTok, Instagram Reels and YouTube Shorts, which is the part that needs approved platform apps - Hosted rendering, so long batches tie up your own machine - Curated templates and caption styles that already look native to each platform - Any built-in sense of what is performing, analytics loops and hook variants - Someone else absorbing model and stock-footage cost changes If one of those is essential to you, that is the reason to keep paying for Reel Farm, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Reel Farm The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me bulk short-form video generation, built so the output is labelled and the volume is bounded to replace Reel Farm. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The category this belongs to is content farming, and the honest description of the default version is a spam machine. Before building it, two constraints that are not optional. First, labelling. The EU AI Act's transparency obligations under Article 50 have applied since 2 August 2026, with the marking and detection requirements in Article 50(2) moving to 2 December 2026 under the Omnibus proposal. Synthetic content must be marked in a machine readable way and disclosed to a person on first exposure. Penalties reach fifteen million euros or three percent of worldwide turnover. Every major platform also has its own disclosure requirement. So the build marks output at generation, sets the platform's own disclosure flag on upload, and has no path that publishes unlabelled. Second, volume. Every platform's terms prohibit bulk automated posting, and the accounts that do it get removed. A rate limit you can raise is a rate limit you will raise, so the cap is a constant with a low value and the tool tells you what it declined to post. If what you actually want is a tool that mass-produces unlabelled content to game a feed, this build will not be it, and that is deliberate rather than an oversight. ### Stack (fixed, do not substitute) - Sourcing: Content from sources you record with a licence · images and clips in a published video are someone's work - Rendering: ffmpeg with a declarative scene description · reproducible renders matter more than a flexible editor - Captions: Timings derived from synthesised speech, never estimated · captions estimated from reading speed drift within a minute - Marking: Provenance metadata embedded at render, plus platform disclosure on upload · the obligation is machine readable marking and human disclosure, and they are different things - Publishing: A rate-limited queue against official APIs only · automation outside the official API is what gets accounts removed ### Data model (create this before Phase 1) - topics(id, text, created_at) - scripts(id, topic_id, body, model, generated_at, reviewed_at, reviewed_by) · nothing renders unreviewed - assets(id, kind, path, source, licence_id, licence_allows_commercial, licence_allows_derivative) - renders(id, script_id, scene_json, output_path, duration_ms, synthetic_marked, at) - captions(render_id, ordinal, start_ms, end_ms, text) · from speech timings, never estimated - posts(id, render_id, platform, account_id, disclosure_set, scheduled_for, posted_at, external_id, declined_reason) - rate_state(platform, account_id, window_start, posted_count) · the cap, enforced in the publish path - A render carries its synthetic marking as a stored fact, so a post whose render is unmarked cannot be published. ### Phase 1 · Scripts a person has read Build: Generate a script from a topic, hold it for review, and require an explicit approval before it can be rendered. Record who approved it and when. Make the review fast enough that it is not the thing people route around. Trap: A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. Done when: A script cannot be rendered without a recorded approval, enforced in the render path rather than the interface. Approving a batch of ten takes under two minutes, timed by hand. The approver and time are recorded per script. Rejecting a script records the reason. Do not build yet: No assets, rendering or publishing. ### Phase 2 · Assets you are allowed to use Build: Store assets with a licence record covering commercial use and derivative works. Refuse to include an asset whose licence does not permit the use. Record the source for every asset including generated ones. Trap: Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. Done when: An asset without a licence record cannot be included in a render, tested by attempting it. Licences distinguish commercial use from derivative works, since they are different permissions. Generated assets record the model and prompt. A render's asset list is exportable as a provenance document. Do not build yet: No rendering or captions yet. ### Phase 3 · Rendering, with captions that stay in sync Build: Render from a declarative scene description so the same inputs produce the same file. Derive caption timings from the synthesised speech's own word timings rather than estimating from reading speed. Verify sync at the end of a long video. Trap: Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. Done when: Captions match the speech at the end of a two minute video, verified by watching. The same scene description renders byte-identically twice. Caption timings come from speech word timings rather than estimation, verified by inspecting the pipeline. Rendering fifty videos does not exhaust memory or disk unexpectedly. Do not build yet: No marking or publishing yet. ### Phase 4 · Marking, which is not optional Build: Embed provenance metadata marking the content as AI generated at render time. Set the platform's own synthetic content flag on upload. Make an unmarked render unpublishable in the publish path rather than by convention. Trap: Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. Done when: Every render carries machine readable provenance metadata identifying it as AI generated, verified by reading it back from the file. The platform disclosure flag is set on every upload. An unmarked render cannot be published, tested by crafting one. Removing the marking from a file makes it unpublishable rather than merely warning. Do not build yet: No publishing rate limits yet. ### Phase 5 · Publishing slowly, on purpose Build: Publish through official APIs only, with a per-account daily cap enforced as a constant. Queue and decline rather than exceeding it. Report declined posts with the reason. Handle the platform rejecting or removing a post. Trap: A configurable rate limit becomes whatever the impatient version of you sets it to at midnight, and the account is removed a week later. Make the cap a constant in the publish path, report what was declined, and treat wanting a higher number as a signal about the strategy rather than a configuration problem. Done when: The daily cap is enforced as a constant and cannot be raised at runtime, verified by attempting it. Posts beyond the cap are declined with a recorded reason rather than queued indefinitely. Publishing uses the official API only. A post removed by the platform is detected and reported rather than silently forgotten. Do not build yet: Nothing after this. ### Out of scope (and why) - Any path that publishes unlabelled synthetic content, which is the feature this category is usually built around. - Automation outside official APIs, which violates every platform's terms and gets accounts removed. - Using a real person's likeness or voice, which is a consent question this build does not attempt to answer. - Volume beyond the cap. If the strategy needs more posts than this allows, the strategy is the thing to reconsider. ### README must contain - That synthetic marking is embedded at render and the platform disclosure flag is set separately, because they are two obligations - That an unmarked render cannot be published, enforced in the publish path - That the posting cap is a constant and cannot be raised at runtime - That every asset needs a licence record distinguishing commercial from derivative use - That caption timings come from the speech rather than being estimated ===== AGENTS.md ===== # Agent instructions · Reel Farm indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Content from sources you record with a licence, ffmpeg with a declarative scene description, Timings derived from synthesised speech, never estimated, Provenance metadata embedded at render, plus platform disclosure on upload, A rate-limited queue against official APIs only. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. - Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. - Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. - Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. ===== BUILD_PLAN.md ===== # Build plan · Reel Farm A pipeline turning a topic into short-form video posts, where the interesting engineering is the render pipeline and caption timing, and the design constraints are that every output is labelled as synthetic and the posting rate is bounded by a constant you cannot raise at runtime. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Scripts a person has read Nothing reaches a render without a human having looked at it. ### Steps 1. Require approval in the render path Not in the interface. Interface gates get bypassed by scripts. Files: `src/render.py` 2. Make approving fast A slow review is a review that gets removed. Files: `src/review.py` 3. Record approver and reason Both approvals and rejections. The rejections are the useful data. Files: `src/scripts.py` ### Done when - [ ] Rendering without approval is impossible - [ ] Ten scripts review in under two minutes - [ ] Approver and time are recorded - [ ] Rejections record a reason ### Watch out - A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. ## Phase 2 · Assets you are allowed to use Every image and clip in an output has a recorded licence permitting it. ### Steps 1. Require a licence record per asset Enforced at inclusion. Volume turns one mistake into hundreds. Files: `src/assets.py` 2. Distinguish commercial from derivative Different permissions. A licence allowing one may forbid the other. Files: `src/licences.py` 3. Record generated asset provenance Model and prompt. Not an absent question, a different one. Files: `src/assets.py` ### Done when - [ ] An unlicensed asset cannot be included - [ ] Commercial and derivative permissions are separate - [ ] Generated assets record model and prompt - [ ] A render exports its asset provenance ### Watch out - Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. ## Phase 3 · Rendering, with captions that stay in sync A video renders reproducibly and its captions match the speech. ### Steps 1. Render from a declarative scene Reproducible. The same inputs must give the same file. Files: `src/render.py` 2. Take caption timings from the speech Never estimate. Estimation drifts within a minute. Files: `src/captions.py`, `tests/test_sync.py` 3. Verify sync at the end, not the start Drift accumulates. The first ten seconds always look fine. Files: `tests/test_sync.py` ### Done when - [ ] Captions match speech at the end of a long video - [ ] The same scene renders byte-identically - [ ] Timings come from speech, not estimation - [ ] Fifty renders complete without resource surprises ### Watch out - Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. ## Phase 4 · Marking, which is not optional Every output is machine readable as synthetic and discloses itself to a viewer. ### Steps 1. Embed provenance at render Machine readable, in the file. Not a database column. Files: `src/marking.py` 2. Set the platform disclosure flag A separate obligation from the metadata. Both are required. Files: `src/publish.py` 3. Block unmarked in the publish path Enforced where publishing happens, tested with a crafted unmarked file. Files: `src/publish.py`, `tests/test_marking.py` ### Done when - [ ] Every render carries readable provenance metadata - [ ] The platform disclosure flag is set on upload - [ ] An unmarked render cannot be published - [ ] Stripping the marking blocks publication ### Watch out - Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. ## Not in this build - Publishing unlabelled synthetic content. - Automation outside official APIs. - Using a real person's likeness or voice. - Volume beyond the cap. ## After v1, if you want it - Performance data fed back into which scripts get approved, which is the only honest way to improve output - Multi-platform rendering from one scene, respecting each platform's requirements - A provenance page for published videos, which is where disclosure becomes useful rather than only compliant ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Per account. Enforced as a constant in the publish path, not read from configuration at runtime. DAILY_POST_CAP=3 # Required. Present as a constant so the answer is visible. There is no other value. REQUIRE_SCRIPT_APPROVAL=always # Required. Embedded at render. An unmarked file is unpublishable. SYNTHETIC_MARKING=always # Required. Output files, each carrying provenance metadata. RENDER_DIR=./renders # Required · secret. Official API only. Anything else violates the terms. PLATFORM_API_KEY=a platform-issued key # Required. Provides the word timings captions are built from. Estimation is not an option. TTS_ENGINE=local
You are building a lean indie version of Reel Farm. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Reel Farm · indie build A pipeline turning a topic into short-form video posts, where the interesting engineering is the render pipeline and caption timing, and the design constraints are that every output is labelled as synthetic and the posting rate is bounded by a constant you cannot raise at runtime. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Sourcing | Content from sources you record with a licence | images and clips in a published video are someone's work | | Rendering | ffmpeg with a declarative scene description | reproducible renders matter more than a flexible editor | | Captions | Timings derived from synthesised speech, never estimated | captions estimated from reading speed drift within a minute | | Marking | Provenance metadata embedded at render, plus platform disclosure on upload | the obligation is machine readable marking and human disclosure, and they are different things | | Publishing | A rate-limited queue against official APIs only | automation outside the official API is what gets accounts removed | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Python 3.12 or newer** · free - Why: Orchestrates generation, rendering and publishing. - Get it: python.org or your package manager, then a virtual environment. - Verify: python3 --version prints 3.12 or higher - [ ] **ffmpeg with the codecs the platforms accept** · free - Why: Rendering, and each platform has its own container, codec and aspect requirements. - Get it: Your package manager, then check each target platform's published encoding requirements. - Verify: A rendered file uploads to your target platform without re-encoding - [ ] **The disclosure rules that apply to you** · free to read - Why: The EU transparency obligations have applied since 2 August 2026 with marking requirements following, and every platform has its own rule. The build enforces both, so you need to know what both say. - Get it: Read the transparency obligations for your jurisdiction and the synthetic content policy of every platform you will post to. Write down what each requires. - Verify: A written note states the marking and disclosure requirements per jurisdiction and platform - [ ] **Official API access for one platform** · free, but approval takes time - Why: Publishing through anything other than the official API violates the terms and removes accounts, and API access often needs review before it is granted. - Get it: Apply for developer access on one platform and read its rate limits and synthetic content requirements before building the publish path. - Verify: A test post publishes through the official API with the disclosure flag set - [ ] **A licensed asset library** · free for your own work; a subscription for a stock library - Why: Volume turns one unlicensed image into hundreds of infringing posts, and the licence record is required before an asset can be used. - Get it: Assets you created, or a library whose licence explicitly permits commercial derivative use. Save the licence text with them. - Verify: Twenty assets exist, each with a licence record permitting your use ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Publishing unlabelled synthetic content. - Automation outside official APIs. - Using a real person's likeness or voice. - Volume beyond the cap. - Scheduled auto-posting to TikTok, Instagram Reels and YouTube Shorts, which is the part that needs approved platform apps - Hosted rendering, so long batches tie up your own machine - Curated templates and caption styles that already look native to each platform - Any built-in sense of what is performing, analytics loops and hook variants - Someone else absorbing model and stock-footage cost changes If one of those is essential to you, that is the reason to keep paying for Reel Farm, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Reel Farm The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me bulk short-form video generation, built so the output is labelled and the volume is bounded to replace Reel Farm. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The category this belongs to is content farming, and the honest description of the default version is a spam machine. Before building it, two constraints that are not optional. First, labelling. The EU AI Act's transparency obligations under Article 50 have applied since 2 August 2026, with the marking and detection requirements in Article 50(2) moving to 2 December 2026 under the Omnibus proposal. Synthetic content must be marked in a machine readable way and disclosed to a person on first exposure. Penalties reach fifteen million euros or three percent of worldwide turnover. Every major platform also has its own disclosure requirement. So the build marks output at generation, sets the platform's own disclosure flag on upload, and has no path that publishes unlabelled. Second, volume. Every platform's terms prohibit bulk automated posting, and the accounts that do it get removed. A rate limit you can raise is a rate limit you will raise, so the cap is a constant with a low value and the tool tells you what it declined to post. If what you actually want is a tool that mass-produces unlabelled content to game a feed, this build will not be it, and that is deliberate rather than an oversight. ### Stack (fixed, do not substitute) - Sourcing: Content from sources you record with a licence · images and clips in a published video are someone's work - Rendering: ffmpeg with a declarative scene description · reproducible renders matter more than a flexible editor - Captions: Timings derived from synthesised speech, never estimated · captions estimated from reading speed drift within a minute - Marking: Provenance metadata embedded at render, plus platform disclosure on upload · the obligation is machine readable marking and human disclosure, and they are different things - Publishing: A rate-limited queue against official APIs only · automation outside the official API is what gets accounts removed ### Data model (create this before Phase 1) - topics(id, text, created_at) - scripts(id, topic_id, body, model, generated_at, reviewed_at, reviewed_by) · nothing renders unreviewed - assets(id, kind, path, source, licence_id, licence_allows_commercial, licence_allows_derivative) - renders(id, script_id, scene_json, output_path, duration_ms, synthetic_marked, at) - captions(render_id, ordinal, start_ms, end_ms, text) · from speech timings, never estimated - posts(id, render_id, platform, account_id, disclosure_set, scheduled_for, posted_at, external_id, declined_reason) - rate_state(platform, account_id, window_start, posted_count) · the cap, enforced in the publish path - A render carries its synthetic marking as a stored fact, so a post whose render is unmarked cannot be published. ### Phase 1 · Scripts a person has read Build: Generate a script from a topic, hold it for review, and require an explicit approval before it can be rendered. Record who approved it and when. Make the review fast enough that it is not the thing people route around. Trap: A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. Done when: A script cannot be rendered without a recorded approval, enforced in the render path rather than the interface. Approving a batch of ten takes under two minutes, timed by hand. The approver and time are recorded per script. Rejecting a script records the reason. Do not build yet: No assets, rendering or publishing. ### Phase 2 · Assets you are allowed to use Build: Store assets with a licence record covering commercial use and derivative works. Refuse to include an asset whose licence does not permit the use. Record the source for every asset including generated ones. Trap: Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. Done when: An asset without a licence record cannot be included in a render, tested by attempting it. Licences distinguish commercial use from derivative works, since they are different permissions. Generated assets record the model and prompt. A render's asset list is exportable as a provenance document. Do not build yet: No rendering or captions yet. ### Phase 3 · Rendering, with captions that stay in sync Build: Render from a declarative scene description so the same inputs produce the same file. Derive caption timings from the synthesised speech's own word timings rather than estimating from reading speed. Verify sync at the end of a long video. Trap: Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. Done when: Captions match the speech at the end of a two minute video, verified by watching. The same scene description renders byte-identically twice. Caption timings come from speech word timings rather than estimation, verified by inspecting the pipeline. Rendering fifty videos does not exhaust memory or disk unexpectedly. Do not build yet: No marking or publishing yet. ### Phase 4 · Marking, which is not optional Build: Embed provenance metadata marking the content as AI generated at render time. Set the platform's own synthetic content flag on upload. Make an unmarked render unpublishable in the publish path rather than by convention. Trap: Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. Done when: Every render carries machine readable provenance metadata identifying it as AI generated, verified by reading it back from the file. The platform disclosure flag is set on every upload. An unmarked render cannot be published, tested by crafting one. Removing the marking from a file makes it unpublishable rather than merely warning. Do not build yet: No publishing rate limits yet. ### Phase 5 · Publishing slowly, on purpose Build: Publish through official APIs only, with a per-account daily cap enforced as a constant. Queue and decline rather than exceeding it. Report declined posts with the reason. Handle the platform rejecting or removing a post. Trap: A configurable rate limit becomes whatever the impatient version of you sets it to at midnight, and the account is removed a week later. Make the cap a constant in the publish path, report what was declined, and treat wanting a higher number as a signal about the strategy rather than a configuration problem. Done when: The daily cap is enforced as a constant and cannot be raised at runtime, verified by attempting it. Posts beyond the cap are declined with a recorded reason rather than queued indefinitely. Publishing uses the official API only. A post removed by the platform is detected and reported rather than silently forgotten. Do not build yet: Nothing after this. ### Out of scope (and why) - Any path that publishes unlabelled synthetic content, which is the feature this category is usually built around. - Automation outside official APIs, which violates every platform's terms and gets accounts removed. - Using a real person's likeness or voice, which is a consent question this build does not attempt to answer. - Volume beyond the cap. If the strategy needs more posts than this allows, the strategy is the thing to reconsider. ### README must contain - That synthetic marking is embedded at render and the platform disclosure flag is set separately, because they are two obligations - That an unmarked render cannot be published, enforced in the publish path - That the posting cap is a constant and cannot be raised at runtime - That every asset needs a licence record distinguishing commercial from derivative use - That caption timings come from the speech rather than being estimated ===== AGENTS.md ===== # Agent instructions · Reel Farm indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Content from sources you record with a licence, ffmpeg with a declarative scene description, Timings derived from synthesised speech, never estimated, Provenance metadata embedded at render, plus platform disclosure on upload, A rate-limited queue against official APIs only. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. - Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. - Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. - Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. ===== BUILD_PLAN.md ===== # Build plan · Reel Farm A pipeline turning a topic into short-form video posts, where the interesting engineering is the render pipeline and caption timing, and the design constraints are that every output is labelled as synthetic and the posting rate is bounded by a constant you cannot raise at runtime. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Scripts a person has read Nothing reaches a render without a human having looked at it. ### Steps 1. Require approval in the render path Not in the interface. Interface gates get bypassed by scripts. Files: `src/render.py` 2. Make approving fast A slow review is a review that gets removed. Files: `src/review.py` 3. Record approver and reason Both approvals and rejections. The rejections are the useful data. Files: `src/scripts.py` ### Done when - [ ] Rendering without approval is impossible - [ ] Ten scripts review in under two minutes - [ ] Approver and time are recorded - [ ] Rejections record a reason ### Watch out - A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. ## Phase 2 · Assets you are allowed to use Every image and clip in an output has a recorded licence permitting it. ### Steps 1. Require a licence record per asset Enforced at inclusion. Volume turns one mistake into hundreds. Files: `src/assets.py` 2. Distinguish commercial from derivative Different permissions. A licence allowing one may forbid the other. Files: `src/licences.py` 3. Record generated asset provenance Model and prompt. Not an absent question, a different one. Files: `src/assets.py` ### Done when - [ ] An unlicensed asset cannot be included - [ ] Commercial and derivative permissions are separate - [ ] Generated assets record model and prompt - [ ] A render exports its asset provenance ### Watch out - Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. ## Phase 3 · Rendering, with captions that stay in sync A video renders reproducibly and its captions match the speech. ### Steps 1. Render from a declarative scene Reproducible. The same inputs must give the same file. Files: `src/render.py` 2. Take caption timings from the speech Never estimate. Estimation drifts within a minute. Files: `src/captions.py`, `tests/test_sync.py` 3. Verify sync at the end, not the start Drift accumulates. The first ten seconds always look fine. Files: `tests/test_sync.py` ### Done when - [ ] Captions match speech at the end of a long video - [ ] The same scene renders byte-identically - [ ] Timings come from speech, not estimation - [ ] Fifty renders complete without resource surprises ### Watch out - Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. ## Phase 4 · Marking, which is not optional Every output is machine readable as synthetic and discloses itself to a viewer. ### Steps 1. Embed provenance at render Machine readable, in the file. Not a database column. Files: `src/marking.py` 2. Set the platform disclosure flag A separate obligation from the metadata. Both are required. Files: `src/publish.py` 3. Block unmarked in the publish path Enforced where publishing happens, tested with a crafted unmarked file. Files: `src/publish.py`, `tests/test_marking.py` ### Done when - [ ] Every render carries readable provenance metadata - [ ] The platform disclosure flag is set on upload - [ ] An unmarked render cannot be published - [ ] Stripping the marking blocks publication ### Watch out - Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. ## Not in this build - Publishing unlabelled synthetic content. - Automation outside official APIs. - Using a real person's likeness or voice. - Volume beyond the cap. ## After v1, if you want it - Performance data fed back into which scripts get approved, which is the only honest way to improve output - Multi-platform rendering from one scene, respecting each platform's requirements - A provenance page for published videos, which is where disclosure becomes useful rather than only compliant ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Per account. Enforced as a constant in the publish path, not read from configuration at runtime. DAILY_POST_CAP=3 # Required. Present as a constant so the answer is visible. There is no other value. REQUIRE_SCRIPT_APPROVAL=always # Required. Embedded at render. An unmarked file is unpublishable. SYNTHETIC_MARKING=always # Required. Output files, each carrying provenance metadata. RENDER_DIR=./renders # Required · secret. Official API only. Anything else violates the terms. PLATFORM_API_KEY=a platform-issued key # Required. Provides the word timings captions are built from. Estimation is not an option. TTS_ENGINE=local
You are building a production product version of Reel Farm. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Reel Farm · product brief ## Problem The pipeline here is not a secret: an LLM writes a short script, a TTS API reads it, stock or generated clips get stitched behind it, and word-level timings drive burned-in captions. ffmpeg does the heavy lifting and an agent can wire the whole chain in a weekend, including a queue that renders fifty variations overnight. Where it stops being fun is everything after the render: scheduled posting to TikTok, Instagram and YouTube means real API access, app review, tokens that expire, and platform rules that change without warning. You will also spend more time than you expect on the boring parts, safe-area layout for captions, loudness normalization, and clips that do not visually repeat every third video. Build it if you want control over the script and the look, pay if the value you actually want is the post button. ## Product outcome A generation pipeline whose output is always labelled as synthetic, whose assets are all licensed, whose captions stay in sync, and which will not post more than a bounded number of times a day whatever you tell it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - ffmpeg and ffprobe installed locally - An LLM API key for scripting - A TTS API key, or a local TTS model - A stock footage API key, or your own clip library - whisper.cpp or a transcription API for word-level caption timings - Disk space and patience for render queues ## Explicit non-goals for v1 - Publishing unlabelled synthetic content. - Automation outside official APIs. - Using a real person's likeness or voice. - Volume beyond the cap. - Scheduled auto-posting to TikTok, Instagram Reels and YouTube Shorts, which is the part that needs approved platform apps - Hosted rendering, so long batches tie up your own machine - Curated templates and caption styles that already look native to each platform - Any built-in sense of what is performing, analytics loops and hook variants - Someone else absorbing model and stock-footage cost changes ## Success criteria - Rendering without script approval is confirmed impossible - An unlicensed asset is confirmed impossible to include - An unmarked render is confirmed unpublishable - The posting cap is confirmed not raisable at runtime - Captions are confirmed in sync at the end of a long video ===== BRIEF.md ===== # Build brief · Reel Farm The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me bulk short-form video generation, built so the output is labelled and the volume is bounded to replace Reel Farm. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The category this belongs to is content farming, and the honest description of the default version is a spam machine. Before building it, two constraints that are not optional. First, labelling. The EU AI Act's transparency obligations under Article 50 have applied since 2 August 2026, with the marking and detection requirements in Article 50(2) moving to 2 December 2026 under the Omnibus proposal. Synthetic content must be marked in a machine readable way and disclosed to a person on first exposure. Penalties reach fifteen million euros or three percent of worldwide turnover. Every major platform also has its own disclosure requirement. So the build marks output at generation, sets the platform's own disclosure flag on upload, and has no path that publishes unlabelled. Second, volume. Every platform's terms prohibit bulk automated posting, and the accounts that do it get removed. A rate limit you can raise is a rate limit you will raise, so the cap is a constant with a low value and the tool tells you what it declined to post. If what you actually want is a tool that mass-produces unlabelled content to game a feed, this build will not be it, and that is deliberate rather than an oversight. ### Stack (fixed, do not substitute) - Sourcing: Content from sources you record with a licence · images and clips in a published video are someone's work - Rendering: ffmpeg with a declarative scene description · reproducible renders matter more than a flexible editor - Captions: Timings derived from synthesised speech, never estimated · captions estimated from reading speed drift within a minute - Marking: Provenance metadata embedded at render, plus platform disclosure on upload · the obligation is machine readable marking and human disclosure, and they are different things - Publishing: A rate-limited queue against official APIs only · automation outside the official API is what gets accounts removed ### Data model (create this before Phase 1) - topics(id, text, created_at) - scripts(id, topic_id, body, model, generated_at, reviewed_at, reviewed_by) · nothing renders unreviewed - assets(id, kind, path, source, licence_id, licence_allows_commercial, licence_allows_derivative) - renders(id, script_id, scene_json, output_path, duration_ms, synthetic_marked, at) - captions(render_id, ordinal, start_ms, end_ms, text) · from speech timings, never estimated - posts(id, render_id, platform, account_id, disclosure_set, scheduled_for, posted_at, external_id, declined_reason) - rate_state(platform, account_id, window_start, posted_count) · the cap, enforced in the publish path - A render carries its synthetic marking as a stored fact, so a post whose render is unmarked cannot be published. ### Phase 1 · Scripts a person has read Build: Generate a script from a topic, hold it for review, and require an explicit approval before it can be rendered. Record who approved it and when. Make the review fast enough that it is not the thing people route around. Trap: A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. Done when: A script cannot be rendered without a recorded approval, enforced in the render path rather than the interface. Approving a batch of ten takes under two minutes, timed by hand. The approver and time are recorded per script. Rejecting a script records the reason. Do not build yet: No assets, rendering or publishing. ### Phase 2 · Assets you are allowed to use Build: Store assets with a licence record covering commercial use and derivative works. Refuse to include an asset whose licence does not permit the use. Record the source for every asset including generated ones. Trap: Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. Done when: An asset without a licence record cannot be included in a render, tested by attempting it. Licences distinguish commercial use from derivative works, since they are different permissions. Generated assets record the model and prompt. A render's asset list is exportable as a provenance document. Do not build yet: No rendering or captions yet. ### Phase 3 · Rendering, with captions that stay in sync Build: Render from a declarative scene description so the same inputs produce the same file. Derive caption timings from the synthesised speech's own word timings rather than estimating from reading speed. Verify sync at the end of a long video. Trap: Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. Done when: Captions match the speech at the end of a two minute video, verified by watching. The same scene description renders byte-identically twice. Caption timings come from speech word timings rather than estimation, verified by inspecting the pipeline. Rendering fifty videos does not exhaust memory or disk unexpectedly. Do not build yet: No marking or publishing yet. ### Phase 4 · Marking, which is not optional Build: Embed provenance metadata marking the content as AI generated at render time. Set the platform's own synthetic content flag on upload. Make an unmarked render unpublishable in the publish path rather than by convention. Trap: Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. Done when: Every render carries machine readable provenance metadata identifying it as AI generated, verified by reading it back from the file. The platform disclosure flag is set on every upload. An unmarked render cannot be published, tested by crafting one. Removing the marking from a file makes it unpublishable rather than merely warning. Do not build yet: No publishing rate limits yet. ### Phase 5 · Publishing slowly, on purpose Build: Publish through official APIs only, with a per-account daily cap enforced as a constant. Queue and decline rather than exceeding it. Report declined posts with the reason. Handle the platform rejecting or removing a post. Trap: A configurable rate limit becomes whatever the impatient version of you sets it to at midnight, and the account is removed a week later. Make the cap a constant in the publish path, report what was declined, and treat wanting a higher number as a signal about the strategy rather than a configuration problem. Done when: The daily cap is enforced as a constant and cannot be raised at runtime, verified by attempting it. Posts beyond the cap are declined with a recorded reason rather than queued indefinitely. Publishing uses the official API only. A post removed by the platform is detected and reported rather than silently forgotten. Do not build yet: Nothing after this. ### Out of scope (and why) - Any path that publishes unlabelled synthetic content, which is the feature this category is usually built around. - Automation outside official APIs, which violates every platform's terms and gets accounts removed. - Using a real person's likeness or voice, which is a consent question this build does not attempt to answer. - Volume beyond the cap. If the strategy needs more posts than this allows, the strategy is the thing to reconsider. ### README must contain - That synthetic marking is embedded at render and the platform disclosure flag is set separately, because they are two obligations - That an unmarked render cannot be published, enforced in the publish path - That the posting cap is a constant and cannot be raised at runtime - That every asset needs a licence record distinguishing commercial from derivative use - That caption timings come from the speech rather than being estimated ===== ARCHITECTURE.md ===== # Architecture · Reel Farm ## Stack | Part | Choice | Why | | --- | --- | --- | | Sourcing | Content from sources you record with a licence | images and clips in a published video are someone's work | | Rendering | ffmpeg with a declarative scene description | reproducible renders matter more than a flexible editor | | Captions | Timings derived from synthesised speech, never estimated | captions estimated from reading speed drift within a minute | | Marking | Provenance metadata embedded at render, plus platform disclosure on upload | the obligation is machine readable marking and human disclosure, and they are different things | | Publishing | A rate-limited queue against official APIs only | automation outside the official API is what gets accounts removed | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Review | Human approval before rendering. | Any interface, provided the gate is in the render path rather than the display. | | Assets | Licence records and provenance. | Any store, provided an unlicensed asset cannot be included. | | Render | Reproducible output and speech-derived captions. | Any renderer, provided timings come from the speech. | | Marking | Embedded provenance and platform disclosure. | Not swappable and not optional. Two separate obligations, two separate checks. | | Publishing | Official APIs within a constant cap. | Any client, provided the cap is not runtime configurable. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DAILY_POST_CAP` · required · Per account. Enforced as a constant in the publish path, not read from configuration at runtime. - `REQUIRE_SCRIPT_APPROVAL` · required · Present as a constant so the answer is visible. There is no other value. - `SYNTHETIC_MARKING` · required · Embedded at render. An unmarked file is unpublishable. - `RENDER_DIR` · required · Output files, each carrying provenance metadata. - `PLATFORM_API_KEY` · required, secret · Official API only. Anything else violates the terms. - `TTS_ENGINE` · required · Provides the word timings captions are built from. Estimation is not an option. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Reel Farm product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Content from sources you record with a licence, ffmpeg with a declarative scene description, Timings derived from synthesised speech, never estimated, Provenance metadata embedded at render, plus platform disclosure on upload, A rate-limited queue against official APIs only. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. - Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. - Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. - Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. - A configurable rate limit becomes whatever the impatient version of you sets it to at midnight, and the account is removed a week later. Make the cap a constant in the publish path, report what was declined, and treat wanting a higher number as a signal about the strategy rather than a configuration problem. ===== MILESTONES.md ===== # Delivery milestones · Reel Farm Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Scripts a person has read Nothing reaches a render without a human having looked at it. ### Steps 1. Require approval in the render path Not in the interface. Interface gates get bypassed by scripts. Files: `src/render.py` 2. Make approving fast A slow review is a review that gets removed. Files: `src/review.py` 3. Record approver and reason Both approvals and rejections. The rejections are the useful data. Files: `src/scripts.py` ### Done when - [ ] Rendering without approval is impossible - [ ] Ten scripts review in under two minutes - [ ] Approver and time are recorded - [ ] Rejections record a reason ### Watch out - A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. ## M2 · Assets you are allowed to use Every image and clip in an output has a recorded licence permitting it. ### Steps 1. Require a licence record per asset Enforced at inclusion. Volume turns one mistake into hundreds. Files: `src/assets.py` 2. Distinguish commercial from derivative Different permissions. A licence allowing one may forbid the other. Files: `src/licences.py` 3. Record generated asset provenance Model and prompt. Not an absent question, a different one. Files: `src/assets.py` ### Done when - [ ] An unlicensed asset cannot be included - [ ] Commercial and derivative permissions are separate - [ ] Generated assets record model and prompt - [ ] A render exports its asset provenance ### Watch out - Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. ## M3 · Rendering, with captions that stay in sync A video renders reproducibly and its captions match the speech. ### Steps 1. Render from a declarative scene Reproducible. The same inputs must give the same file. Files: `src/render.py` 2. Take caption timings from the speech Never estimate. Estimation drifts within a minute. Files: `src/captions.py`, `tests/test_sync.py` 3. Verify sync at the end, not the start Drift accumulates. The first ten seconds always look fine. Files: `tests/test_sync.py` ### Done when - [ ] Captions match speech at the end of a long video - [ ] The same scene renders byte-identically - [ ] Timings come from speech, not estimation - [ ] Fifty renders complete without resource surprises ### Watch out - Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. ## M4 · Marking, which is not optional Every output is machine readable as synthetic and discloses itself to a viewer. ### Steps 1. Embed provenance at render Machine readable, in the file. Not a database column. Files: `src/marking.py` 2. Set the platform disclosure flag A separate obligation from the metadata. Both are required. Files: `src/publish.py` 3. Block unmarked in the publish path Enforced where publishing happens, tested with a crafted unmarked file. Files: `src/publish.py`, `tests/test_marking.py` ### Done when - [ ] Every render carries readable provenance metadata - [ ] The platform disclosure flag is set on upload - [ ] An unmarked render cannot be published - [ ] Stripping the marking blocks publication ### Watch out - Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. ## M5 · Publishing slowly, on purpose (production only) The tool posts within limits and tells you what it declined. ### Steps 1. Enforce the cap as a constant Not configuration. A raisable limit gets raised. Files: `src/ratelimit.py` 2. Decline and report With the reason. An infinite queue hides the problem. Files: `src/publish.py` 3. Detect removals A post taken down is information about your content, not a transient error. Files: `src/monitor.py` ### Done when - [ ] The cap cannot be raised at runtime - [ ] Excess posts are declined with a reason - [ ] Publishing uses the official API only - [ ] Platform removals are detected and reported ### Watch out - A configurable rate limit becomes whatever the impatient version of you sets it to at midnight, and the account is removed a week later. Make the cap a constant in the publish path, report what was declined, and treat wanting a higher number as a signal about the strategy rather than a configuration problem. ===== OPERATIONS.md ===== # Operations · Reel Farm ## Backup Scripts, asset licences and provenance records are what let you answer a question about a published video months later. The renders themselves regenerate from the scene descriptions. ## Restore Restore and confirm asset licence records survived, since a render whose licence references are lost should be treated as unpublishable rather than assumed fine. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Declined post count and platform removal rate. A rising removal rate means the content is being judged as spam by the platform, which is information about the strategy rather than a technical fault to route around. ## Incident checklist An unlabelled video reaching a platform is the failure that matters, and it means the publish path check was bypassed. That check is enforced against the file rather than a database flag precisely so this is hard to do accidentally. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Rendering without script approval is confirmed impossible - [ ] An unlicensed asset is confirmed impossible to include - [ ] An unmarked render is confirmed unpublishable - [ ] The posting cap is confirmed not raisable at runtime - [ ] Captions are confirmed in sync at the end of a long video ## Launch constraint Do not market omitted Reel Farm capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Per account. Enforced as a constant in the publish path, not read from configuration at runtime. DAILY_POST_CAP=3 # Required. Present as a constant so the answer is visible. There is no other value. REQUIRE_SCRIPT_APPROVAL=always # Required. Embedded at render. An unmarked file is unpublishable. SYNTHETIC_MARKING=always # Required. Output files, each carrying provenance metadata. RENDER_DIR=./renders # Required · secret. Official API only. Anything else violates the terms. PLATFORM_API_KEY=a platform-issued key # Required. Provides the word timings captions are built from. Estimation is not an option. TTS_ENGINE=local
# Reel Farm · indie build A pipeline turning a topic into short-form video posts, where the interesting engineering is the render pipeline and caption timing, and the design constraints are that every output is labelled as synthetic and the posting rate is bounded by a constant you cannot raise at runtime. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Sourcing | Content from sources you record with a licence | images and clips in a published video are someone's work | | Rendering | ffmpeg with a declarative scene description | reproducible renders matter more than a flexible editor | | Captions | Timings derived from synthesised speech, never estimated | captions estimated from reading speed drift within a minute | | Marking | Provenance metadata embedded at render, plus platform disclosure on upload | the obligation is machine readable marking and human disclosure, and they are different things | | Publishing | A rate-limited queue against official APIs only | automation outside the official API is what gets accounts removed | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Python 3.12 or newer** · free - Why: Orchestrates generation, rendering and publishing. - Get it: python.org or your package manager, then a virtual environment. - Verify: python3 --version prints 3.12 or higher - [ ] **ffmpeg with the codecs the platforms accept** · free - Why: Rendering, and each platform has its own container, codec and aspect requirements. - Get it: Your package manager, then check each target platform's published encoding requirements. - Verify: A rendered file uploads to your target platform without re-encoding - [ ] **The disclosure rules that apply to you** · free to read - Why: The EU transparency obligations have applied since 2 August 2026 with marking requirements following, and every platform has its own rule. The build enforces both, so you need to know what both say. - Get it: Read the transparency obligations for your jurisdiction and the synthetic content policy of every platform you will post to. Write down what each requires. - Verify: A written note states the marking and disclosure requirements per jurisdiction and platform - [ ] **Official API access for one platform** · free, but approval takes time - Why: Publishing through anything other than the official API violates the terms and removes accounts, and API access often needs review before it is granted. - Get it: Apply for developer access on one platform and read its rate limits and synthetic content requirements before building the publish path. - Verify: A test post publishes through the official API with the disclosure flag set - [ ] **A licensed asset library** · free for your own work; a subscription for a stock library - Why: Volume turns one unlicensed image into hundreds of infringing posts, and the licence record is required before an asset can be used. - Get it: Assets you created, or a library whose licence explicitly permits commercial derivative use. Save the licence text with them. - Verify: Twenty assets exist, each with a licence record permitting your use ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Publishing unlabelled synthetic content. - Automation outside official APIs. - Using a real person's likeness or voice. - Volume beyond the cap. - Scheduled auto-posting to TikTok, Instagram Reels and YouTube Shorts, which is the part that needs approved platform apps - Hosted rendering, so long batches tie up your own machine - Curated templates and caption styles that already look native to each platform - Any built-in sense of what is performing, analytics loops and hook variants - Someone else absorbing model and stock-footage cost changes If one of those is essential to you, that is the reason to keep paying for Reel Farm, and the README should say so rather than pretend.
# Build brief · Reel Farm The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me bulk short-form video generation, built so the output is labelled and the volume is bounded to replace Reel Farm. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The category this belongs to is content farming, and the honest description of the default version is a spam machine. Before building it, two constraints that are not optional. First, labelling. The EU AI Act's transparency obligations under Article 50 have applied since 2 August 2026, with the marking and detection requirements in Article 50(2) moving to 2 December 2026 under the Omnibus proposal. Synthetic content must be marked in a machine readable way and disclosed to a person on first exposure. Penalties reach fifteen million euros or three percent of worldwide turnover. Every major platform also has its own disclosure requirement. So the build marks output at generation, sets the platform's own disclosure flag on upload, and has no path that publishes unlabelled. Second, volume. Every platform's terms prohibit bulk automated posting, and the accounts that do it get removed. A rate limit you can raise is a rate limit you will raise, so the cap is a constant with a low value and the tool tells you what it declined to post. If what you actually want is a tool that mass-produces unlabelled content to game a feed, this build will not be it, and that is deliberate rather than an oversight. ### Stack (fixed, do not substitute) - Sourcing: Content from sources you record with a licence · images and clips in a published video are someone's work - Rendering: ffmpeg with a declarative scene description · reproducible renders matter more than a flexible editor - Captions: Timings derived from synthesised speech, never estimated · captions estimated from reading speed drift within a minute - Marking: Provenance metadata embedded at render, plus platform disclosure on upload · the obligation is machine readable marking and human disclosure, and they are different things - Publishing: A rate-limited queue against official APIs only · automation outside the official API is what gets accounts removed ### Data model (create this before Phase 1) - topics(id, text, created_at) - scripts(id, topic_id, body, model, generated_at, reviewed_at, reviewed_by) · nothing renders unreviewed - assets(id, kind, path, source, licence_id, licence_allows_commercial, licence_allows_derivative) - renders(id, script_id, scene_json, output_path, duration_ms, synthetic_marked, at) - captions(render_id, ordinal, start_ms, end_ms, text) · from speech timings, never estimated - posts(id, render_id, platform, account_id, disclosure_set, scheduled_for, posted_at, external_id, declined_reason) - rate_state(platform, account_id, window_start, posted_count) · the cap, enforced in the publish path - A render carries its synthetic marking as a stored fact, so a post whose render is unmarked cannot be published. ### Phase 1 · Scripts a person has read Build: Generate a script from a topic, hold it for review, and require an explicit approval before it can be rendered. Record who approved it and when. Make the review fast enough that it is not the thing people route around. Trap: A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. Done when: A script cannot be rendered without a recorded approval, enforced in the render path rather than the interface. Approving a batch of ten takes under two minutes, timed by hand. The approver and time are recorded per script. Rejecting a script records the reason. Do not build yet: No assets, rendering or publishing. ### Phase 2 · Assets you are allowed to use Build: Store assets with a licence record covering commercial use and derivative works. Refuse to include an asset whose licence does not permit the use. Record the source for every asset including generated ones. Trap: Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. Done when: An asset without a licence record cannot be included in a render, tested by attempting it. Licences distinguish commercial use from derivative works, since they are different permissions. Generated assets record the model and prompt. A render's asset list is exportable as a provenance document. Do not build yet: No rendering or captions yet. ### Phase 3 · Rendering, with captions that stay in sync Build: Render from a declarative scene description so the same inputs produce the same file. Derive caption timings from the synthesised speech's own word timings rather than estimating from reading speed. Verify sync at the end of a long video. Trap: Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. Done when: Captions match the speech at the end of a two minute video, verified by watching. The same scene description renders byte-identically twice. Caption timings come from speech word timings rather than estimation, verified by inspecting the pipeline. Rendering fifty videos does not exhaust memory or disk unexpectedly. Do not build yet: No marking or publishing yet. ### Phase 4 · Marking, which is not optional Build: Embed provenance metadata marking the content as AI generated at render time. Set the platform's own synthetic content flag on upload. Make an unmarked render unpublishable in the publish path rather than by convention. Trap: Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. Done when: Every render carries machine readable provenance metadata identifying it as AI generated, verified by reading it back from the file. The platform disclosure flag is set on every upload. An unmarked render cannot be published, tested by crafting one. Removing the marking from a file makes it unpublishable rather than merely warning. Do not build yet: No publishing rate limits yet. ### Phase 5 · Publishing slowly, on purpose Build: Publish through official APIs only, with a per-account daily cap enforced as a constant. Queue and decline rather than exceeding it. Report declined posts with the reason. Handle the platform rejecting or removing a post. Trap: A configurable rate limit becomes whatever the impatient version of you sets it to at midnight, and the account is removed a week later. Make the cap a constant in the publish path, report what was declined, and treat wanting a higher number as a signal about the strategy rather than a configuration problem. Done when: The daily cap is enforced as a constant and cannot be raised at runtime, verified by attempting it. Posts beyond the cap are declined with a recorded reason rather than queued indefinitely. Publishing uses the official API only. A post removed by the platform is detected and reported rather than silently forgotten. Do not build yet: Nothing after this. ### Out of scope (and why) - Any path that publishes unlabelled synthetic content, which is the feature this category is usually built around. - Automation outside official APIs, which violates every platform's terms and gets accounts removed. - Using a real person's likeness or voice, which is a consent question this build does not attempt to answer. - Volume beyond the cap. If the strategy needs more posts than this allows, the strategy is the thing to reconsider. ### README must contain - That synthetic marking is embedded at render and the platform disclosure flag is set separately, because they are two obligations - That an unmarked render cannot be published, enforced in the publish path - That the posting cap is a constant and cannot be raised at runtime - That every asset needs a licence record distinguishing commercial from derivative use - That caption timings come from the speech rather than being estimated
# Agent instructions · Reel Farm indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Content from sources you record with a licence, ffmpeg with a declarative scene description, Timings derived from synthesised speech, never estimated, Provenance metadata embedded at render, plus platform disclosure on upload, A rate-limited queue against official APIs only. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. - Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. - Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. - Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting.
# Build plan · Reel Farm A pipeline turning a topic into short-form video posts, where the interesting engineering is the render pipeline and caption timing, and the design constraints are that every output is labelled as synthetic and the posting rate is bounded by a constant you cannot raise at runtime. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Scripts a person has read Nothing reaches a render without a human having looked at it. ### Steps 1. Require approval in the render path Not in the interface. Interface gates get bypassed by scripts. Files: `src/render.py` 2. Make approving fast A slow review is a review that gets removed. Files: `src/review.py` 3. Record approver and reason Both approvals and rejections. The rejections are the useful data. Files: `src/scripts.py` ### Done when - [ ] Rendering without approval is impossible - [ ] Ten scripts review in under two minutes - [ ] Approver and time are recorded - [ ] Rejections record a reason ### Watch out - A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. ## Phase 2 · Assets you are allowed to use Every image and clip in an output has a recorded licence permitting it. ### Steps 1. Require a licence record per asset Enforced at inclusion. Volume turns one mistake into hundreds. Files: `src/assets.py` 2. Distinguish commercial from derivative Different permissions. A licence allowing one may forbid the other. Files: `src/licences.py` 3. Record generated asset provenance Model and prompt. Not an absent question, a different one. Files: `src/assets.py` ### Done when - [ ] An unlicensed asset cannot be included - [ ] Commercial and derivative permissions are separate - [ ] Generated assets record model and prompt - [ ] A render exports its asset provenance ### Watch out - Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. ## Phase 3 · Rendering, with captions that stay in sync A video renders reproducibly and its captions match the speech. ### Steps 1. Render from a declarative scene Reproducible. The same inputs must give the same file. Files: `src/render.py` 2. Take caption timings from the speech Never estimate. Estimation drifts within a minute. Files: `src/captions.py`, `tests/test_sync.py` 3. Verify sync at the end, not the start Drift accumulates. The first ten seconds always look fine. Files: `tests/test_sync.py` ### Done when - [ ] Captions match speech at the end of a long video - [ ] The same scene renders byte-identically - [ ] Timings come from speech, not estimation - [ ] Fifty renders complete without resource surprises ### Watch out - Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. ## Phase 4 · Marking, which is not optional Every output is machine readable as synthetic and discloses itself to a viewer. ### Steps 1. Embed provenance at render Machine readable, in the file. Not a database column. Files: `src/marking.py` 2. Set the platform disclosure flag A separate obligation from the metadata. Both are required. Files: `src/publish.py` 3. Block unmarked in the publish path Enforced where publishing happens, tested with a crafted unmarked file. Files: `src/publish.py`, `tests/test_marking.py` ### Done when - [ ] Every render carries readable provenance metadata - [ ] The platform disclosure flag is set on upload - [ ] An unmarked render cannot be published - [ ] Stripping the marking blocks publication ### Watch out - Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. ## Not in this build - Publishing unlabelled synthetic content. - Automation outside official APIs. - Using a real person's likeness or voice. - Volume beyond the cap. ## After v1, if you want it - Performance data fed back into which scripts get approved, which is the only honest way to improve output - Multi-platform rendering from one scene, respecting each platform's requirements - A provenance page for published videos, which is where disclosure becomes useful rather than only compliant
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Per account. Enforced as a constant in the publish path, not read from configuration at runtime. DAILY_POST_CAP=3 # Required. Present as a constant so the answer is visible. There is no other value. REQUIRE_SCRIPT_APPROVAL=always # Required. Embedded at render. An unmarked file is unpublishable. SYNTHETIC_MARKING=always # Required. Output files, each carrying provenance metadata. RENDER_DIR=./renders # Required · secret. Official API only. Anything else violates the terms. PLATFORM_API_KEY=a platform-issued key # Required. Provides the word timings captions are built from. Estimation is not an option. TTS_ENGINE=local
# Reel Farm · product brief ## Problem The pipeline here is not a secret: an LLM writes a short script, a TTS API reads it, stock or generated clips get stitched behind it, and word-level timings drive burned-in captions. ffmpeg does the heavy lifting and an agent can wire the whole chain in a weekend, including a queue that renders fifty variations overnight. Where it stops being fun is everything after the render: scheduled posting to TikTok, Instagram and YouTube means real API access, app review, tokens that expire, and platform rules that change without warning. You will also spend more time than you expect on the boring parts, safe-area layout for captions, loudness normalization, and clips that do not visually repeat every third video. Build it if you want control over the script and the look, pay if the value you actually want is the post button. ## Product outcome A generation pipeline whose output is always labelled as synthetic, whose assets are all licensed, whose captions stay in sync, and which will not post more than a bounded number of times a day whatever you tell it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - ffmpeg and ffprobe installed locally - An LLM API key for scripting - A TTS API key, or a local TTS model - A stock footage API key, or your own clip library - whisper.cpp or a transcription API for word-level caption timings - Disk space and patience for render queues ## Explicit non-goals for v1 - Publishing unlabelled synthetic content. - Automation outside official APIs. - Using a real person's likeness or voice. - Volume beyond the cap. - Scheduled auto-posting to TikTok, Instagram Reels and YouTube Shorts, which is the part that needs approved platform apps - Hosted rendering, so long batches tie up your own machine - Curated templates and caption styles that already look native to each platform - Any built-in sense of what is performing, analytics loops and hook variants - Someone else absorbing model and stock-footage cost changes ## Success criteria - Rendering without script approval is confirmed impossible - An unlicensed asset is confirmed impossible to include - An unmarked render is confirmed unpublishable - The posting cap is confirmed not raisable at runtime - Captions are confirmed in sync at the end of a long video
# Build brief · Reel Farm The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me bulk short-form video generation, built so the output is labelled and the volume is bounded to replace Reel Farm. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The category this belongs to is content farming, and the honest description of the default version is a spam machine. Before building it, two constraints that are not optional. First, labelling. The EU AI Act's transparency obligations under Article 50 have applied since 2 August 2026, with the marking and detection requirements in Article 50(2) moving to 2 December 2026 under the Omnibus proposal. Synthetic content must be marked in a machine readable way and disclosed to a person on first exposure. Penalties reach fifteen million euros or three percent of worldwide turnover. Every major platform also has its own disclosure requirement. So the build marks output at generation, sets the platform's own disclosure flag on upload, and has no path that publishes unlabelled. Second, volume. Every platform's terms prohibit bulk automated posting, and the accounts that do it get removed. A rate limit you can raise is a rate limit you will raise, so the cap is a constant with a low value and the tool tells you what it declined to post. If what you actually want is a tool that mass-produces unlabelled content to game a feed, this build will not be it, and that is deliberate rather than an oversight. ### Stack (fixed, do not substitute) - Sourcing: Content from sources you record with a licence · images and clips in a published video are someone's work - Rendering: ffmpeg with a declarative scene description · reproducible renders matter more than a flexible editor - Captions: Timings derived from synthesised speech, never estimated · captions estimated from reading speed drift within a minute - Marking: Provenance metadata embedded at render, plus platform disclosure on upload · the obligation is machine readable marking and human disclosure, and they are different things - Publishing: A rate-limited queue against official APIs only · automation outside the official API is what gets accounts removed ### Data model (create this before Phase 1) - topics(id, text, created_at) - scripts(id, topic_id, body, model, generated_at, reviewed_at, reviewed_by) · nothing renders unreviewed - assets(id, kind, path, source, licence_id, licence_allows_commercial, licence_allows_derivative) - renders(id, script_id, scene_json, output_path, duration_ms, synthetic_marked, at) - captions(render_id, ordinal, start_ms, end_ms, text) · from speech timings, never estimated - posts(id, render_id, platform, account_id, disclosure_set, scheduled_for, posted_at, external_id, declined_reason) - rate_state(platform, account_id, window_start, posted_count) · the cap, enforced in the publish path - A render carries its synthetic marking as a stored fact, so a post whose render is unmarked cannot be published. ### Phase 1 · Scripts a person has read Build: Generate a script from a topic, hold it for review, and require an explicit approval before it can be rendered. Record who approved it and when. Make the review fast enough that it is not the thing people route around. Trap: A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. Done when: A script cannot be rendered without a recorded approval, enforced in the render path rather than the interface. Approving a batch of ten takes under two minutes, timed by hand. The approver and time are recorded per script. Rejecting a script records the reason. Do not build yet: No assets, rendering or publishing. ### Phase 2 · Assets you are allowed to use Build: Store assets with a licence record covering commercial use and derivative works. Refuse to include an asset whose licence does not permit the use. Record the source for every asset including generated ones. Trap: Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. Done when: An asset without a licence record cannot be included in a render, tested by attempting it. Licences distinguish commercial use from derivative works, since they are different permissions. Generated assets record the model and prompt. A render's asset list is exportable as a provenance document. Do not build yet: No rendering or captions yet. ### Phase 3 · Rendering, with captions that stay in sync Build: Render from a declarative scene description so the same inputs produce the same file. Derive caption timings from the synthesised speech's own word timings rather than estimating from reading speed. Verify sync at the end of a long video. Trap: Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. Done when: Captions match the speech at the end of a two minute video, verified by watching. The same scene description renders byte-identically twice. Caption timings come from speech word timings rather than estimation, verified by inspecting the pipeline. Rendering fifty videos does not exhaust memory or disk unexpectedly. Do not build yet: No marking or publishing yet. ### Phase 4 · Marking, which is not optional Build: Embed provenance metadata marking the content as AI generated at render time. Set the platform's own synthetic content flag on upload. Make an unmarked render unpublishable in the publish path rather than by convention. Trap: Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. Done when: Every render carries machine readable provenance metadata identifying it as AI generated, verified by reading it back from the file. The platform disclosure flag is set on every upload. An unmarked render cannot be published, tested by crafting one. Removing the marking from a file makes it unpublishable rather than merely warning. Do not build yet: No publishing rate limits yet. ### Phase 5 · Publishing slowly, on purpose Build: Publish through official APIs only, with a per-account daily cap enforced as a constant. Queue and decline rather than exceeding it. Report declined posts with the reason. Handle the platform rejecting or removing a post. Trap: A configurable rate limit becomes whatever the impatient version of you sets it to at midnight, and the account is removed a week later. Make the cap a constant in the publish path, report what was declined, and treat wanting a higher number as a signal about the strategy rather than a configuration problem. Done when: The daily cap is enforced as a constant and cannot be raised at runtime, verified by attempting it. Posts beyond the cap are declined with a recorded reason rather than queued indefinitely. Publishing uses the official API only. A post removed by the platform is detected and reported rather than silently forgotten. Do not build yet: Nothing after this. ### Out of scope (and why) - Any path that publishes unlabelled synthetic content, which is the feature this category is usually built around. - Automation outside official APIs, which violates every platform's terms and gets accounts removed. - Using a real person's likeness or voice, which is a consent question this build does not attempt to answer. - Volume beyond the cap. If the strategy needs more posts than this allows, the strategy is the thing to reconsider. ### README must contain - That synthetic marking is embedded at render and the platform disclosure flag is set separately, because they are two obligations - That an unmarked render cannot be published, enforced in the publish path - That the posting cap is a constant and cannot be raised at runtime - That every asset needs a licence record distinguishing commercial from derivative use - That caption timings come from the speech rather than being estimated
# Architecture · Reel Farm ## Stack | Part | Choice | Why | | --- | --- | --- | | Sourcing | Content from sources you record with a licence | images and clips in a published video are someone's work | | Rendering | ffmpeg with a declarative scene description | reproducible renders matter more than a flexible editor | | Captions | Timings derived from synthesised speech, never estimated | captions estimated from reading speed drift within a minute | | Marking | Provenance metadata embedded at render, plus platform disclosure on upload | the obligation is machine readable marking and human disclosure, and they are different things | | Publishing | A rate-limited queue against official APIs only | automation outside the official API is what gets accounts removed | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Review | Human approval before rendering. | Any interface, provided the gate is in the render path rather than the display. | | Assets | Licence records and provenance. | Any store, provided an unlicensed asset cannot be included. | | Render | Reproducible output and speech-derived captions. | Any renderer, provided timings come from the speech. | | Marking | Embedded provenance and platform disclosure. | Not swappable and not optional. Two separate obligations, two separate checks. | | Publishing | Official APIs within a constant cap. | Any client, provided the cap is not runtime configurable. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DAILY_POST_CAP` · required · Per account. Enforced as a constant in the publish path, not read from configuration at runtime. - `REQUIRE_SCRIPT_APPROVAL` · required · Present as a constant so the answer is visible. There is no other value. - `SYNTHETIC_MARKING` · required · Embedded at render. An unmarked file is unpublishable. - `RENDER_DIR` · required · Output files, each carrying provenance metadata. - `PLATFORM_API_KEY` · required, secret · Official API only. Anything else violates the terms. - `TTS_ENGINE` · required · Provides the word timings captions are built from. Estimation is not an option. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Reel Farm product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Content from sources you record with a licence, ffmpeg with a declarative scene description, Timings derived from synthesised speech, never estimated, Provenance metadata embedded at render, plus platform disclosure on upload, A rate-limited queue against official APIs only. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. - Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. - Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. - Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. - A configurable rate limit becomes whatever the impatient version of you sets it to at midnight, and the account is removed a week later. Make the cap a constant in the publish path, report what was declined, and treat wanting a higher number as a signal about the strategy rather than a configuration problem.
# Delivery milestones · Reel Farm Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Scripts a person has read Nothing reaches a render without a human having looked at it. ### Steps 1. Require approval in the render path Not in the interface. Interface gates get bypassed by scripts. Files: `src/render.py` 2. Make approving fast A slow review is a review that gets removed. Files: `src/review.py` 3. Record approver and reason Both approvals and rejections. The rejections are the useful data. Files: `src/scripts.py` ### Done when - [ ] Rendering without approval is impossible - [ ] Ten scripts review in under two minutes - [ ] Approver and time are recorded - [ ] Rejections record a reason ### Watch out - A review step that is slow or tedious gets skipped by the person who added it, usually within a fortnight, and the tool reverts to publishing whatever the model produced. Make approving a good script take seconds, so the friction lands only on the ones that need attention. ## M2 · Assets you are allowed to use Every image and clip in an output has a recorded licence permitting it. ### Steps 1. Require a licence record per asset Enforced at inclusion. Volume turns one mistake into hundreds. Files: `src/assets.py` 2. Distinguish commercial from derivative Different permissions. A licence allowing one may forbid the other. Files: `src/licences.py` 3. Record generated asset provenance Model and prompt. Not an absent question, a different one. Files: `src/assets.py` ### Done when - [ ] An unlicensed asset cannot be included - [ ] Commercial and derivative permissions are separate - [ ] Generated assets record model and prompt - [ ] A render exports its asset provenance ### Watch out - Pulling images from search results puts unlicensed photographs into published videos, and the volume this tool produces turns one mistake into hundreds of infringing posts before anyone notices. Require a licence record before an asset can be used, and treat a generated image as having a provenance question rather than no question. ## M3 · Rendering, with captions that stay in sync A video renders reproducibly and its captions match the speech. ### Steps 1. Render from a declarative scene Reproducible. The same inputs must give the same file. Files: `src/render.py` 2. Take caption timings from the speech Never estimate. Estimation drifts within a minute. Files: `src/captions.py`, `tests/test_sync.py` 3. Verify sync at the end, not the start Drift accumulates. The first ten seconds always look fine. Files: `tests/test_sync.py` ### Done when - [ ] Captions match speech at the end of a long video - [ ] The same scene renders byte-identically - [ ] Timings come from speech, not estimation - [ ] Fifty renders complete without resource surprises ### Watch out - Estimating caption timing from word count and an assumed reading speed drifts audibly within the first minute, and by the end of a two minute video the captions are a full phrase behind. Take the timings from the speech synthesis itself, which knows exactly when each word was spoken. ## M4 · Marking, which is not optional Every output is machine readable as synthetic and discloses itself to a viewer. ### Steps 1. Embed provenance at render Machine readable, in the file. Not a database column. Files: `src/marking.py` 2. Set the platform disclosure flag A separate obligation from the metadata. Both are required. Files: `src/publish.py` 3. Block unmarked in the publish path Enforced where publishing happens, tested with a crafted unmarked file. Files: `src/publish.py`, `tests/test_marking.py` ### Done when - [ ] Every render carries readable provenance metadata - [ ] The platform disclosure flag is set on upload - [ ] An unmarked render cannot be published - [ ] Stripping the marking blocks publication ### Watch out - Treating the platform's disclosure toggle as sufficient misses the machine readable marking requirement, and treating the embedded metadata as sufficient misses the human disclosure. They are two separate obligations and you need both, so implement them as two checks rather than one setting. ## M5 · Publishing slowly, on purpose (production only) The tool posts within limits and tells you what it declined. ### Steps 1. Enforce the cap as a constant Not configuration. A raisable limit gets raised. Files: `src/ratelimit.py` 2. Decline and report With the reason. An infinite queue hides the problem. Files: `src/publish.py` 3. Detect removals A post taken down is information about your content, not a transient error. Files: `src/monitor.py` ### Done when - [ ] The cap cannot be raised at runtime - [ ] Excess posts are declined with a reason - [ ] Publishing uses the official API only - [ ] Platform removals are detected and reported ### Watch out - A configurable rate limit becomes whatever the impatient version of you sets it to at midnight, and the account is removed a week later. Make the cap a constant in the publish path, report what was declined, and treat wanting a higher number as a signal about the strategy rather than a configuration problem.
# Operations · Reel Farm ## Backup Scripts, asset licences and provenance records are what let you answer a question about a published video months later. The renders themselves regenerate from the scene descriptions. ## Restore Restore and confirm asset licence records survived, since a render whose licence references are lost should be treated as unpublishable rather than assumed fine. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Declined post count and platform removal rate. A rising removal rate means the content is being judged as spam by the platform, which is information about the strategy rather than a technical fault to route around. ## Incident checklist An unlabelled video reaching a platform is the failure that matters, and it means the publish path check was bypassed. That check is enforced against the file rather than a database flag precisely so this is hard to do accidentally. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Rendering without script approval is confirmed impossible - [ ] An unlicensed asset is confirmed impossible to include - [ ] An unmarked render is confirmed unpublishable - [ ] The posting cap is confirmed not raisable at runtime - [ ] Captions are confirmed in sync at the end of a long video ## Launch constraint Do not market omitted Reel Farm capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Per account. Enforced as a constant in the publish path, not read from configuration at runtime. DAILY_POST_CAP=3 # Required. Present as a constant so the answer is visible. There is no other value. REQUIRE_SCRIPT_APPROVAL=always # Required. Embedded at render. An unmarked file is unpublishable. SYNTHETIC_MARKING=always # Required. Output files, each carrying provenance metadata. RENDER_DIR=./renders # Required · secret. Official API only. Anything else violates the terms. PLATFORM_API_KEY=a platform-issued key # Required. Provides the word timings captions are built from. Estimation is not an option. TTS_ENGINE=local
$ choose a build depth, inspect the files, then open the complete pack in your agent
Because a faceless content operation is not one video, it is two hundred, and the friction that kills it is scheduling and upload, not generation. Getting write access to the major short-form platforms is a bureaucratic slog that nobody wants to do twice, and a hosted tool that already holds those tokens is worth a subscription to people running this as a volume game. There is also the honest fact that the default output of a DIY pipeline looks like a DIY pipeline for the first week, until you fix the caption placement, the loudness, and the clip repetition. If you are making a handful of videos with a specific look you care about, build it. If you are farming, pay.
xScheduled auto-posting to TikTok, Instagram Reels and YouTube Shorts, which is the part that needs approved platform apps
xHosted rendering, so long batches tie up your own machine
xCurated templates and caption styles that already look native to each platform
xAny built-in sense of what is performing, analytics loops and hook variants
xSomeone else absorbing model and stock-footage cost changes
Nothing worth pointing at. That's why the prompt exists.
Vibecode Reel Farm
Kinda. The core of Reel Farm is buildable in a weekend with the prompt on this page, but there are real gaps: Scheduled auto-posting to TikTok, Instagram Reels and YouTube Shorts, which is the part that needs approved platform apps, Hosted rendering, so long batches tie up your own machine. Read the honest list above before committing.
How much does Reel Farm cost?
Reel Farm costs about $95/month (Scale, checked 2026-08-18), which is $1140 per year.
What do I lose by replacing Reel Farm?
Honestly: Scheduled auto-posting to TikTok, Instagram Reels and YouTube Shorts, which is the part that needs approved platform apps; Hosted rendering, so long batches tie up your own machine; Curated templates and caption styles that already look native to each platform; Any built-in sense of what is performing, analytics loops and hook variants; Someone else absorbing model and stock-footage cost changes. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Reel Farm?
No mature open-source alternative worth pointing at, which is exactly why the one-shot prompt on this page exists.