An automated quote-video engine for Instagram, TikTok, and YouTube.
It fetches a quote, selects a background image, picks a local background-music track, renders centered text over a dark gradient, and exports a 10-second vertical video. Platform payloads are generated in a shared structure modeled after anime-content-engine, while Instagram and YouTube workflows follow the publishing patterns from reels-engine and shorts-engine.
The repository is organized around the same top-level split used in anime-content-engine:
config/constant.jsfor shared runtime constantsconstants/for fonts, music, image fallback, and platform definitionsservices/for quote fetch, image fetch, audio selection, rendering, video assembly, attribution formatting, and metadata generationmodules/for platform-facing entrypoints and publisher integrationsscripts/for local utility commands, including token helpers underscripts/tokens/data/memory/for per-platform content memory snapshots used to reduce recent repeats.github/workflows/for scheduled automationutils/for logging, env parsing, randomization, downloads, and delay helpers
The shared automation flow does this:
- Fetch a quote from ZenQuotes with retry support
- Fetch a portrait background from Unsplash, or fall back to
resources/images/background.jpg - Pick a local background-music
.wavfile fromresources/music - Render a dark gradient text overlay
- Export a 10-second vertical MP4 with FFmpeg
- Build platform-aware metadata with structured attribution
- Record the generated content in per-platform memory after a successful publish
Generated files are written to:
output/<platform>.mp4output/payload.<platform>.jsonoutput/temp/
Payloads keep the existing video contract while now carrying:
- a shared
contentobject for future content types - structured
imageandaudioattribution data - a backward-compatible
soundalias for the selected background-music asset - platform-specific
postContentfields for titles, descriptions, captions, tags, and hashtags
Each remote platform keeps its own 90-day memory file in data/memory/:
youtube.jsoninstagram.jsontiktok.json
Duplicate checks happen before generation, but memory is only finalized after a successful publish. In GitHub Actions, the updated platform memory file is committed back to the repository so the memory survives across runs. Repository workflow permissions must allow contents: write for memory commits and issues: write for TikTok draft reminders.
npm install
# Local proof-of-concept generation only
npm run generate:local
# Generate platform packages
npm run generate:instagram
npm run generate:tiktok
npm run generate:youtube
npm run generate:all
# Publish flows used by GitHub Actions
npm run post:instagram
npm run post:tiktok
npm run post:youtube
# Local OAuth token helpers
npm run token:instagram
npm run token:tiktok
npm run token:youtube
# Cleanup
npm run flush:results
npm run memory:flush
npm run memory:flush:youtube
npm run memory:flush:instagram
npm run memory:flush:tiktok
# Formatting and linting
npm run format
npm run lintIf you want to run the GitHub Actions locally, use act.
Examples:
# TikTok generate-and-publish workflow
act workflow_dispatch -W .github/workflows/tiktok-post-content.yml \
-s TIKTOK_CLIENT_KEY='your-client-key' \
-s TIKTOK_CLIENT_SECRET='your-client-secret' \
-s TIKTOK_REFRESH_TOKEN='your-refresh-token' \
-s UNSPLASH_ACCESS_KEY='your-unsplash-key'
# YouTube generate-and-publish workflow
act workflow_dispatch -W .github/workflows/youtube-post-content.yml \
-s CLIENT_SECRET_JSON='{"installed":{"client_id":"...","client_secret":"...","redirect_uris":["http://localhost"]}}' \
-s REFRESH_TOKEN='your-refresh-token' \
-s UNSPLASH_ACCESS_KEY='your-unsplash-key'
# Instagram generate-and-publish workflow
act workflow_dispatch -W .github/workflows/instagram-post-content.yml \
-s INSTAGRAM_USER_ID='your-instagram-user-id' \
-s INSTAGRAM_ACCESS_TOKEN='your-access-token' \
-s UNSPLASH_ACCESS_KEY='your-unsplash-key'The Instagram workflow now prefers resumable file upload from output/instagram.mp4, which avoids Meta needing to fetch a public video_url. GitHub release/CDN steps are only used when INSTAGRAM_UPLOAD_SOURCE=url. Artifact upload steps are still skipped in local act runs to avoid GitHub-only behavior.
Metadata is now generated through a shared content pipeline with platform-specific strategy:
- YouTube favors concise, keyword-aware titles and readable descriptions with attribution and a small hashtag set
- Instagram favors polished captions with a readable hook, the quote, a light prompt, and a tighter hashtag mix
- TikTok favors short, searchable captions with a compact hook and relevant niche hashtags
Hashtags are selected from platform-specific core and rotational pools, deduplicated, and kept intentionally small. The system avoids generic TikTok growth-bait tags like #fyp.
UNSPLASH_ACCESS_KEY=
# Instagram publishing
INSTAGRAM_USER_ID=
INSTAGRAM_ACCESS_TOKEN=
INSTAGRAM_LONG_LIVED_ACCESS_TOKEN=
INSTAGRAM_SHORT_LIVED_ACCESS_TOKEN=
INSTAGRAM_APP_ID=
INSTAGRAM_APP_SECRET=
INSTAGRAM_UPLOAD_SOURCE=
INSTAGRAM_VIDEO_PATH=
INSTAGRAM_VIDEO_URL=
# YouTube publishing
CLIENT_SECRET_JSON=
REFRESH_TOKEN=
YOUTUBE_VIDEO_PATH=
# TikTok publishing
TIKTOK_CLIENT_KEY=
TIKTOK_CLIENT_SECRET=
TIKTOK_REFRESH_TOKEN=
TIKTOK_VIDEO_PATH=
TIKTOK_POST_TITLE=
# Optional local overrides
QUOTE_TEXT=
QUOTE_AUTHOR=
MUSIC_ID=instagram-post-content.ymlGenerates the reel and publishes it via the Instagram Graph API using resumable file upload by default. URL-based upload remains available whenINSTAGRAM_UPLOAD_SOURCE=url.youtube-post-content.ymlGenerates the short and uploads it directly through the YouTube Data API.tiktok-post-content.ymlGenerates the TikTok-ready package, refreshes a user access token fromTIKTOK_REFRESH_TOKEN, uploads the MP4 into TikTok draft flow, and creates or updates a GitHub issue when the draft is ready for manual publish.
GitHub Actions scheduled workflows run in UTC. The workflow cron expressions below are aligned to Pacific Daylight Time (UTC-7) and will shift by one hour during Pacific Standard Time unless the schedules are adjusted later.
| Platform | Intended PT | Workflow UTC Cron | DST Note |
|---|---|---|---|
| YouTube | Tuesday 6:00 PM PT | 0 1 * * 3 |
PDT-aligned; shifts during PST |
| YouTube | Wednesday 7:00 PM PT | 0 2 * * 4 |
PDT-aligned; shifts during PST |
| YouTube | Friday 1:00 PM PT | 0 20 * * 5 |
PDT-aligned; shifts during PST |
| YouTube | Saturday 11:00 AM PT | 0 18 * * 6 |
PDT-aligned; shifts during PST |
| TikTok | Monday 6:00 PM PT | 0 1 * * 2 |
PDT-aligned; shifts during PST |
| TikTok | Tuesday 6:00 PM PT | 0 1 * * 3 |
PDT-aligned; shifts during PST |
| TikTok | Wednesday 6:00 PM PT | 0 1 * * 4 |
PDT-aligned; shifts during PST |
| TikTok | Thursday 6:00 PM PT | 0 1 * * 5 |
PDT-aligned; shifts during PST |
| TikTok | Friday 4:00 PM PT | 0 23 * * 5 |
PDT-aligned; shifts during PST |
| Tuesday 5:00 PM PT | 0 0 * * 3 |
PDT-aligned; shifts during PST | |
| Wednesday 5:00 PM PT | 0 0 * * 4 |
PDT-aligned; shifts during PST | |
| Thursday 4:30 PM PT | 30 23 * * 4 |
PDT-aligned; shifts during PST | |
| Friday 4:00 PM PT | 0 23 * * 5 |
PDT-aligned; shifts during PST | |
| Saturday 11:00 AM PT | 0 18 * * 6 |
PDT-aligned; shifts during PST | |
| Sunday 1:00 PM PT | 0 20 * * 0 |
PDT-aligned; shifts during PST |
- Logging uses structured JSON events through
utils/logEvent.js - Metadata is generated through shared builders with platform-specific title, caption, description, hashtag, and tag strategies
- Each remote platform keeps its own 90-day memory file under
data/memory/to reduce exact and near-duplicate posts - GitHub Actions commit only the relevant memory JSON file after a successful publish so memory persists across runs
- If Unsplash is unavailable, the engine falls back to the bundled local background image
- Instagram publishing defaults to
INSTAGRAM_UPLOAD_SOURCE=file, which is more reliable in GitHub Actions because Meta does not need to fetch the reel from a public URL - TikTok uploads currently go to draft, and the workflow opens or updates a GitHub issue as the manual publish reminder
- Close the TikTok draft issue after you review the draft in TikTok and publish it manually
- TikTok may rotate the refresh token during token refresh; if the workflow logs a rotation warning, update the
TIKTOK_REFRESH_TOKENGitHub secret before the next run
- Add optional Piper voiceover generation as a separate audio layer
- Extend the audio mix stage to support background music plus voiceover output
- Reuse the shared metadata and attribution pipeline for additional content types beyond quotes