Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Repost Studio

A free, fully local pipeline for branded short-form video: paste a video URL → download it → crop + brand it on a 1080×1920 canvas (your logo, name, @handle, hook text) → schedule it → deliver to YouTube (API upload) and Instagram (Reels API publish), or push it to Android phones over USB for manual posting. Runs entirely on your machine — no VPS, no SaaS, $0/month.


Table of contents

  1. How it works
  2. What you need
  3. First-time setup
  4. A tour of the app
  5. Step by step: your first video
  6. Connecting YouTube
  7. Connecting Instagram
  8. Optional: the phone-push lane
  9. Customizing
  10. Where your data lives
  11. Daily workflow
  12. Troubleshooting
  13. Costs & limits

1. How it works

Two services run side by side on your machine:

Service Port Role
Studio (frontend/) 3000 The app you work in: link board, brand kit, video editor/canvas, schedule UI
Worker (server/) 8080 The always-on half: watches the Incoming folder, runs the post queue + scheduler, talks to the YouTube/Instagram APIs, pushes to phones over adb
You find a clip (YouTube / Instagram / TikTok / X)
      │
      ▼
┌─────────────────────┐
│ Board (Studio)      │  ← paste URL + caption idea + notes, per niche tab
└─────────┬───────────┘
          ▼
┌─────────────────────┐
│ Media (Studio)      │  ← yt-dlp downloads the file LOCALLY (your home IP,
│                     │    which is why it works where cloud scrapers fail)
│  1080×1920 canvas   │  ← black bg · your logo/name/@handle · hook caption ·
│                     │    drag/resize crop box · trim · zoom
└─────────┬───────────┘
          ▼  Export (WebCodecs render in the browser, ~12 Mbps MP4)
┌─────────────────────┐
│ Worker Incoming     │  ← ~/repost-incoming (the watch folder)
└─────────┬───────────┘
          ▼
┌─────────────────────┐
│ Schedule (Studio)   │  ← pick file + destination + date/time
│ Scheduler (Worker)  │  ← every 15s: deliver anything due
└─────────┬───────────┘
          ├─▶ YouTube   — official resumable upload (free API quota)
          ├─▶ Instagram — official Reels publish, bytes uploaded straight
          │               from your machine (no public hosting needed)
          └─▶ Phone     — adb push → you tap "post" on the phone

Everything is stored locally: a SQLite file for the Studio, JSON files for the worker. Both self-create on first run — there is no database to install.


2. What you need

Required (all free):

  • macOS / Linux / Windows with Node.js 22.x (node -v to check; brew install node@22 on a Mac)
  • Python 3 on macOS/Linux (python3 --version) — yt-dlp ships as a Python zipapp there. Already present on any recent macOS.
  • That's it. ffmpeg and yt-dlp are bundled as npm packages — nothing to install system-wide.

Optional, per delivery lane:

  • YouTube posting — a Google account + a free Google Cloud project (§6)
  • Instagram posting — an Instagram professional account (Business or Creator — flip it in the IG app under Settings → Account type) + a free Meta developer app (§7)
  • Phone lane — an Android phone with USB debugging + adb (§8)

3. First-time setup

git clone <your-repo-url> && cd video-automation   # skip if you have it

cp .env.example .env        # defaults work out of the box — edit later for APIs

npm --prefix frontend install
npm --prefix server install

./start-dev.sh              # starts BOTH: studio :3000 + worker :8080

Open http://localhost:3000. You should see the Brand Kit landing page. The worker's log appears in the same terminal; Ctrl-C stops both.

The worker's Incoming watch folder defaults to ~/repost-incoming (created automatically). Override with PHONEDECK_WATCH_DIR in .env.


4. A tour of the app

The left sidebar has five sections:

  • Deck — the phone rig (only relevant for the phone lane): connected devices, the Incoming file list, push buttons, phone/theme/account management, scrcpy screen mirroring.
  • Brand Kit — your identity, used on every branded post: display name, @handle, verified-badge toggle, and a logo library (uploads are stored locally). No logo yet? Posts render a colored initial-letter avatar.
  • Media — the editor. Paste a URL (or upload a local file), fetch, then position the crop box on the canvas, trim, zoom, write the hook caption (type @ for the emoji picker). Two formats: Twitter (black background, post-style header — the branded look) and Caption (white, caption-only). Export renders the 1080×1920 MP4 and drops it into the worker's Incoming folder automatically.
  • Board — a lightweight spreadsheet for triaging clip links before you make anything: URL, caption idea, context, notes, posted/unusable flags. Three niche tabs; rows auto-expire after 5 days. The floating board widget on the Media tab can send a row straight into the editor, and the row is auto-marked Posted once its export is pushed/delivered.
  • Schedule — the queue. Pick a rendered file, a destination (YouTube account / Instagram account / phone theme), caption/title and a time. The worker delivers it when the time comes. Failed items show the error and a Retry button. Dry run runs everything except the final publish — use it to test credentials safely.

5. Step by step: your first video

  1. Start the app: ./start-dev.sh, open http://localhost:3000.
  2. Brand Kit → set display name + @handle, upload a square logo, Save.
  3. Media → paste a video URL → press the arrow. Wait a few seconds — the file downloads locally (title/author fill in automatically).
  4. Position it: drag the box edges to crop, scroll/pinch to zoom, drag the trim handles under the canvas to cut the clip, type the hook caption.
  5. Press Export and wait for the progress bar. The MP4 lands in ~/repost-incoming (if the worker is down it falls back to a browser download so nothing is lost).
  6. Schedule → New scheduled post → select the file → destination YouTubeadd accountConnect this account (do §6 first) → pick a time a few minutes ahead → tick Dry runSchedule.
  7. Watch the item flip pending → delivered with "dry run ok". That proves auth + upload plumbing end to end without posting anything.
  8. Re-schedule the same file with Dry run unticked. That one goes live.

6. Connecting YouTube

One-time, ~10 minutes, free.

  1. Go to console.cloud.google.com → create a project (any name).
  2. APIs & Services → Library → search YouTube Data API v3Enable.
  3. APIs & Services → OAuth consent screen → External → fill the two required fields → add your own Google account under Test users.
  4. APIs & Services → Credentials → Create credentials → OAuth client ID → Application type Web application → under Authorized redirect URIs add exactly:
    http://localhost:8080/api/oauth/youtube/callback
    
  5. Copy the Client ID and Client secret into .env:
    GOOGLE_CLIENT_ID=...
    GOOGLE_CLIENT_SECRET=...
    
    Restart ./start-dev.sh.
  6. In Schedule → destination YouTube → add account (type the channel name) → Connect this account → Google sign-in opens → approve → "✅ YouTube account connected."

Two Google gotchas worth knowing:

  • While the consent screen is in Testing status, refresh tokens expire after 7 days — you'd reconnect weekly. Fix: OAuth consent screen → Publish app (to "In production"). You do not need Google's verification for your own use; you'll just click through an "unverified app" warning once when connecting.
  • Uploads from an API project that hasn't passed YouTube's (free) API audit may be locked private regardless of the privacy setting you send. The upload still works — you can flip visibility manually in YouTube Studio, or submit the audit form to lift it permanently.

7. Connecting Instagram

One-time, ~15 minutes, free. Your IG account must be professional (Business or Creator).

  1. Go to developers.facebook.comMy Apps → Create app → choose the setup that includes the Instagram API with Instagram Login product (no Facebook Page needed with this variant).
  2. In the app dashboard, open the Instagram product's API setup with Instagram login → under Set up Instagram business login add the redirect URI exactly:
    http://localhost:8080/api/oauth/instagram/callback
    
  3. Add your account as a tester: App roles → Roles → Add people → Instagram Tester → enter your IG username. Then open Instagram (app or web) → Settings → Website permissions → Apps and websites → Tester invites → accept.
  4. From the Instagram product's API setup page copy the Instagram app ID and Instagram app secret into .env:
    IG_APP_ID=...
    IG_APP_SECRET=...
    
    Restart ./start-dev.sh.
  5. In Schedule → destination Instagram → add accountConnect this account → Instagram sign-in → approve the two permissions → "✅ Instagram account connected."

Notes:

  • The app can stay in Development mode forever for your own accounts — Meta's app review is only needed to onboard strangers.
  • Reels via API: keep exports 9:16 (the canvas already is), 3s–15min, H.264 — the studio's output ticks all three.
  • The long-lived token lasts ~60 days. When deliveries start failing with an auth error, just press Connect this account again.
  • The video bytes upload directly from the worker (resumable upload) — your machine needs no public URL, no tunnel.

8. Optional: the phone-push lane

For accounts you'd rather post manually (or anything the APIs gate):

  1. On the phone: Settings → About → tap Build number 7× → enable USB debugging in Developer options; plug in via USB and accept the RSA prompt.
  2. adb on the computer: brew install android-platform-tools (or use Android Studio's copy) and point .env at it, e.g. PHONEDECK_ADB=~/Library/Android/sdk/platform-tools/adb.
  3. Deck → your phone appears → name it. Create a theme (a group of phones, e.g. one per niche) and add the phone's accounts to it.
  4. Schedule with destination Phone push + the theme — at the scheduled time the video lands in each phone's camera roll (with a media scan so the gallery sees it instantly). Post it from the phone like any normal upload. Every push is also archived under the worker's reel catalogue folder by date/phone.

scrcpy (optional, brew install scrcpy) gives you on-desktop screen control of the phones from the Deck tab.


9. Customizing

  • Niche names — edit frontend/src/lib/niches.ts (one label per line). Board tabs and the widget follow.
  • Brand — everything is in the Brand Kit UI; nothing is hardcoded.
  • Outro clip — the editor's include edit toggle appends an outro to the export. Drop your outro at frontend/public/edit.mp4 (1080×1920 MP4) to use it.
  • Global dry runDELIVERY_DRY_RUN=true in .env forces every delivery into dry-run, no matter what the queue item says. Good while testing credentials.
  • Ports/foldersPHONEDECK_PORT, PHONEDECK_WATCH_DIR, NEXT_PUBLIC_PHONEDECK_URL in .env.

10. Where your data lives

All gitignored, all local, all recreated automatically if deleted:

Path Contents
frontend/data/studio.db SQLite: brand kit, board rows, emoji prefs
frontend/storage/brand-logos/ uploaded logos
frontend/storage/downloads/ downloaded source videos (auto-purged after ~3 days)
~/repost-incoming/ rendered exports awaiting scheduling (the watch folder)
server/data/queue.json the post queue
server/data/credentials.json your platform tokens — never share/commit this
server/data/store.json phones, accounts, themes

Back up = copy those paths. Reset = delete them.


11. Daily workflow

  1. ./start-dev.sh
  2. Triage links into the Board as you find clips during the day.
  3. Batch-produce: send each row to Media, crop + caption, Export.
  4. Schedule each export across your accounts at spread-out times.
  5. Leave the worker running; it delivers on time. Check the queue for green delivered rows (each shows the resulting video URL / media id).

12. Troubleshooting

Symptom Fix
Schedule tab says "Worker not reachable" The worker isn't running — ./start-dev.sh (or npm run dev inside server/)
Download fails on a valid URL Platforms change their sites; update the extractor: npm update youtube-dl-exec in frontend/, restart. Private/region-locked posts can't be fetched.
YouTube video uploaded but private Unaudited-project lock (§6) — flip it public in YouTube Studio or complete the free audit
YouTube connect works, deliveries fail after a week Consent screen still in Testing → publish the app to production and reconnect (§6)
Instagram delivery fails with an auth error after ~2 months Long-lived token expired — press Connect this account again
Instagram container ends in ERROR Check the export: 9:16, ≥3s, H.264 MP4 (studio defaults satisfy this — usually it's a hand-uploaded file)
Export finishes but no file in Schedule Worker was down at export time — the MP4 went to your browser Downloads; drop it into ~/repost-incoming manually
Phone push fails Phone unplugged / USB debugging revoked — check Deck shows state device, re-accept the RSA prompt
Port 3000 or 8080 already taken Stop the other process or change PHONEDECK_PORT / Next's port

13. Costs & limits

Thing Cost Limit that matters
Everything local (studio, worker, SQLite, yt-dlp, ffmpeg) $0 your disk + the machine must be awake for scheduled deliveries
YouTube Data API $0 ~6 uploads/day per project on the default 10k quota (more via free quota request)
Instagram API $0 per-account daily publish cap (Meta-side); tokens refresh every ~60 days
Google Cloud / Meta developer accounts $0 one-time paperwork

A word on rights: reposting content you don't own — even cropped and branded — can draw copyright strikes or account removal. The transformation helps but is not legal immunity. Keep the review step; prefer content you have rights to or permission for.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages