shorts-maker
v0.4.0
Published
Split a video into consecutive shorts titled from its name, add hashtags, and publish them to Facebook — runs locally in your browser.
Maintainers
Readme
shorts-maker
Split a long video into consecutive parts — the whole thing, in order, cut on natural
pauses — each titled Main Title part-N #hashtags, ready to post as a series to YouTube /
Instagram / TikTok.
TypeScript on Bun with a React + Tailwind v4 frontend that Bun bundles and hot-reloads itself — no Vite, no webpack, no PostCSS. No AI, no accounts. The only runtime binary it shells out to is ffmpeg; a 90-minute video processes in seconds.
How it works
video ──► find pauses (ffmpeg silencedetect) ──► split into parts ──► cut (stream-copy) ──► parts/ + captions- Every second is covered — parts are contiguous from 0:00 to the end. No highlight-picking.
- Cuts land on a pause, so parts don't end mid-sentence. (Stream-copy snaps to the nearest keyframe, so a cut may shift a second or two.)
- Part length is a cap, not a quota — a 15-min target splits a 96-min video into 7 parts of ~14 min each (evenly), not 6 full 15-min parts plus a 6-min stub. If even parts would come out under ~12 min, one part is dropped and the rest stretch a little to absorb it.
- Titles = the video name (auto-derived from the file, editable in the form) +
part-N- a random set of hashtags from
hashtags.txt(edit to taste).
- a random set of hashtags from
- Your source is already 1080×1920 (9:16 vertical), so no cropping is needed.
Quick start
You need two things on your machine: Bun and ffmpeg.
curl -fsSL https://bun.sh/install | bash # Bun (skip if installed)
brew install ffmpeg # ffmpeg — macOS; use your package manager elsewhere
brew install yt-dlp # optional — only to import from a video linkyt-dlp is only needed if you paste a link (YouTube, Dailymotion, or a direct video
URL) instead of uploading a file.
YouTube “Sign in to confirm you’re not a bot”? YouTube blocks some anonymous downloads. Point the app at your browser’s login:
bunx shorts-maker --serve --cookies-from-browser chrome(orsafari/firefox/edge/brave), or setSHORTS_MAKER_COOKIES_FROM_BROWSER=chrome. Failed link jobs also retry automatically through alternate players before giving up.
Then run it straight from npm — no clone, no install step:
cd ~/videos # any folder — this is where output/ and config/ will be written
bunx shorts-maker --serve # → http://localhost:4321 (bare `bunx shorts-maker` prints help)--serve starts the app from your current folder: generated clips land in ./output/,
staged uploads in ./uploads/, and your saved credentials in ./config/. Run it wherever
you want that data to live.
shorts-maker --serve [options]
-s, --serve Start the web app (required — nothing runs without it)
-p, --port <n> Port to listen on (default 4321, or $PORT)
-d, --dir <path> Folder to read/write data in (default: current folder,
or $SHORTS_MAKER_DIR)
-v, --version Print the version and exit
-h, --help Show this help and exitFrom source
git clone https://github.com/subasshrestha/ShortsMaker && cd ShortsMaker
bun install
bun dev # development → http://localhost:4321 (hot reload / React Fast Refresh)
bun start # production → builds dist/ then serves the optimized bundleAt runtime it needs Bun + ffmpeg; everything else is bundled.
Using the app
The same server process serves both the React app and the API — one port, no second dev server.
- Pick a source three ways: upload a video (click or drag-and-drop, with a live progress bar), paste a link, or choose one you already have under From uploads.
- Sources are kept, not thrown away. Everything you upload — and every video fetched
from a link — lands in
uploads/and stays there, so you can re-split it without re-uploading or re-downloading. Manage them right in the From uploads tab: each entry shows its size and date, with a trash icon to delete it and a Clear all to empty the folder. Deleting a job never deletes its source video. - The title auto-fills from the filename; adjust it, plus part length,
hashtags-per-part, and the hashtag pool (pre-filled from
hashtags.txt) — all from the form. - Hit Split and watch the logs stream live.
- Every run is saved in the History sidebar — it survives refreshes and restarts. Click a job to replay its logs, preview each part inline, copy captions, download clips, reveal a clip in Finder (when running locally), or delete the whole run (files included).
Output
Each run lands in its own folder under output/:
output/<name>__<timestamp>/
parts/
01_hacker-vs-coder.mp4
01_hacker-vs-coder.txt # "Hacker VS Coder part-1 #tags" — paste-ready caption
02_...
manifest.json # every part with timestamps + captionHashtags are drawn from hashtags.txt — one per line, # lines are
comments. Edit that file to fit your niche.
Publish to social media
Each finished part card has a one-click Publish button. Facebook Pages are supported
today (more platforms are planned — the code is structured to drop them in). Uploads use
Facebook's chunked video upload
(upload_phase start → transfer → finish), with resume + retry and a live progress bar
on the card.
- Open Settings (gear in the sidebar) → Facebook Page, and follow the built-in Setup guide to get a Page ID and a long-lived Page Access Token.
- Paste them in and Save — it verifies with Facebook first and only stores if it works.
(Test connection also checks that the token has the
pages_manage_postspermission.) - On any part card, hit Publish to Facebook. The clip is uploaded to your Page with its caption; the card then shows Posted ✓ with a link, and remembers it.
Credentials are stored locally in config/social.json (gitignored) and are never
returned to the browser or sent anywhere except Facebook's Graph API.
Project layout
src/
cli.ts # `shorts-maker` command: --help/--version/--port/--dir, then serves
server.ts # Bun.serve: React app (dev bundler / prod dist) + REST/SSE API
build.ts # pre-builds frontend → dist/ (Bun.build + Tailwind plugin)
paths.ts # package-asset dirs vs. the user's data dir (cwd)
files.ts # shared fs helpers (safeJoin, uniqueName, video extensions)
jobs/
store.ts # job registry — SQLite (bun:sqlite, output/jobs.db) + SSE subscribers
pipeline/
pipeline.ts # orchestration (probe → pauses → split → cut → titles)
media.ts # ffprobe/ffmpeg wrappers (probe, silence detect, cut)
parts.ts # part model + splitter
social/
config.ts # credential store (config/social.json)
facebook.ts # Graph API verify + video upload
frontend/
index.html # entry — Bun bundles main.tsx from here
main.tsx # React mount + BrowserRouter
App.tsx # routes + app-shell layout
styles.css # Tailwind v4 entry: @import + @theme design tokens
pages/ # routes: NewSplitForm, JobDetail, Settings, Guide
components/ # shared: Sidebar, Brand, Dot, Icons, ConfirmDialog
lib/
api.ts # typed fetch helpers (+ streaming upload)
util.ts # formatting helpers
types.ts # shared Job/Part types
bunfig.toml # registers bun-plugin-tailwind for the dev bundler
hashtags.txt # default hashtag pool (a hashtags.txt in your run folder overrides it)