ttclips — clip reposter for TikTok
Fully local, free pipeline: monitors YouTube channels, detects highlights (whisper
- Gemini), crops clips to vertical 9:16 with dead-center, word-synced highlight captions, and posts to TikTok through Playwright browser automation (cookie auth, no API approval needed).
How it works
YouTube RSS feeds ─────────▶ monitor.py (queue of new videos)
│
yt-dlp + ffmpeg (download, merge + cache reuse)
▼
faster-whisper (transcription + word timestamps)
▼
Gemini (probe → fallback chain) highlight ranking
▼
FFmpeg (crop 9:16 + big centered karaoke captions)
▼
2 pipelines: repost clips OR generated original clips
Gemini highlights + Gemini script → edge-tts narration →
cut_and_crop_clip stock b-roll + captions
(gradient fallback)
▼
Playwright → TikTok upload (cookies)
8 posts/day schedule (config/posting_window.json):
| Time | Type |
|---|---|
| 08:00 | repost |
| 10:00 | repost |
| 12:00 | generated |
| 14:00 | repost |
| 16:00 | generated |
| 18:00 | repost |
| 20:00 | generated |
| 22:00 | repost |
Install as a pip package
pip install . # from a source checkout
ttclips --init # scaffold config/ + .env.example into the current dir
Once published: pip install ttclips. The ttclips command resolves
config/ + data/ from the current working directory, or from
$TTCLIPS_HOME when that variable is set — so an installed CLI can run from
any empty folder:
mkdir D:\ttclips && cd D:\ttclips
ttclips --init
# edit .env (GEMINI_API_KEY), then:
ttclips --monitor
Running from a source checkout keeps working unchanged simply by cd-ing to the
repo root. Related console commands: ttclips-gemini --probe and
ttclips-upload (the internal upload child process).
Releasing to PyPI (auto via GitHub Actions)
Pushing a tag vX.Y.Z automatically builds the wheel + sdist, runs
twine check, and publishes to PyPI (.github/workflows/publish.yml). A
separate validation workflow (.github/workflows/ci.yml) builds + checks the
package on every push to main and every PR — no secrets needed.
Release flow:
-
Bump the version in both
pyproject.tomlandttclips/__init__.py(the release workflow refuses to publish a mismatch). -
Tag and push:
git add . ; git commit -m "Release v0.1.0" git tag v0.1.0 git push origin main --tags
-
Watch "Publish to PyPI" on GitHub → Actions. Once green,
pip install ttclipsworks.
First-time publisher setup (trusted publishing — no API token needed):
- PyPI → Account settings → Publishing
→ Add a pending publisher with:
- Owner:
kamzysert3· Repository:ttclips· Workflow name:publish.yml
- Owner:
- The workflow filename must match exactly — it's part of the OIDC claim.
- The first successful run creates the
ttclipsproject on PyPI automatically.
Setup (Windows)
- Python 3.10+ — verify with
python --version. powershell -ExecutionPolicy Bypass -File scripts\setup.ps1- creates
.venv, installs deps & Playwright chromium - installs ffmpeg via winget if missing and adds its bin dir to your PATH so yt-dlp's video+audio merge finds it
- copies
.env.example→.env
- creates
- Edit
.env→ setGEMINI_API_KEY(free at https://aistudio.google.com/apikey) andPEXELS_API_KEY(free at https://www.pexels.com/api/ — powers stock backgrounds behind generated clips; leave blank and generated clips keep the gradient backplate). - TikTok cookies: log into tiktok.com in Chrome → use the "Get cookies.txt
LOCALLY" extension → export for tiktok.com → save as
data/cookies.txt. Re-export roughly monthly (sessions drift).
Verify
ttclips --validate-session # cookies OK?
ttclips --monitor # build watch queue
ttclips --once --dry-run # full pipeline, no upload
ttclips --once --slot 12:00 # post one slot now
ttclips --once --slot 12:00 --no-upload # render only
ttclips --post-file "data\clips\video.mp4" # post an already-generated clip
ttclips-gemini --probe # ping Gemini model status
Closing flags: --dry-run = skip upload, --no-upload = render but don't post.
--post-file <path> (optional --caption "...") uploads a clip that is already
generated, skipping the entire download/transcribe/rank/encode pipeline — handy
for retrying a stuck upload on a finished clip. On success it records a manual
entry in data/posted.json.
Scheduled runs (Windows Task Scheduler)
| Task | Trigger | Action |
|---|---|---|
| ttclips-monitor | Daily 06:00 | refresh the watch queue (--monitor) |
| ttclips-poster | Hourly 07:00→23:00 | --once (posts only at configured slot times) |
The scheduler posts exactly once per slot per day; run --catchup after the
machine was off for a while to fill missed slots for today. scripts/catchup.ps1
wraps it. When registering a task, set its Start in folder to your ttclips
project dir (or define TTCLIPS_HOME) so config/ and data/ resolve.
Configuration
config/channels.json— channels to monitor. Fields per channel:name,channel_id,enabled,keywords(empty = accept all uploads),max_moments(how many clips to post per video, capped at 5).config/posting_window.json— the 8 daily slots (time,type, and ahintfor generated inspiration).config/tiktok.json— browser/headless/delay/retry settings, default hashtags, caption template. Uploads run in an isolated subprocess with retries; waits grow exponentially (retry_wait_initial_sec,retry_wait_backoff,retry_wait_max_sec) after every failure.user_agentmust stay a recent desktop Chrome UA (the library default Chrome/58 gets TCP-reset by TikTok's WAF);upload_timeout_secgives page loads their budget..env— secrets + models + styling:GEMINI_MODEL+GEMINI_FALLBACK_MODELS(comma list) — tried in order; the runtime probes candidates at startup and promotes the healthiest modelGEMINI_RETRY_TIMEOUT— hard cap on retrying a stuck modelWHISPER_MODEL/WHISPER_DEVICE,TTS_VOICE,COOKIES_FILEPEXELS_API_KEY— stock-video b-roll search for generated clips (free key)- caption knobs:
CAPTION_FONT_SIZE(96),CAPTION_ALIGNMENT(5),CAPTION_MARGINV,CAPTION_OUTLINE,CAPTION_SHADOW,CAPTION_WORDS_PER_LINE,HIGHLIGHT_COLOUR(yellow),IDLE_COLOUR(grey)
Captions
Captions are big (96pt), dead-centered, and karaoke-style: the word currently being spoken is bold in the highlight colour while the rest of the line stays grey. Repost clips use real whisper word timestamps; generated clips re-transcribe their TTS narration with faster-whisper so the karaoke is word-synced too, in exactly the same style as reposts.
State
data/queue.json— discovered videos awaiting processingdata/posted.json— dedup history (never reposts the same moment twice)data/downloads/— merged mp4s (reused on retry, no re-download)data/clips/— rendered vertical clipsdata/broll/— cached stock backgrounds for generated clipsdata/logs/ttclips.log— full log
Resilience
- Gemini 503/429 overload: the probe picks a healthy model at startup and the
fallback chain walks through remaining models; retries use jittered backoff
bounded by
GEMINI_RETRY_TIMEOUT. Older flash models (2.x) return 404 for new free-tier keys — use the ones listed in.env.example. - YouTube bot-checks: intermittent "sign in to confirm you're not a bot". The downloader fails loudly; merging is handled by the winget ffmpeg install.
- Partial failures: a failed Gemini post step doesn't waste a 150 MB download — the cached merged mp4 is reused.
- B-roll (generated clips): backgrounds come from the free Pexels Videos API
(
PEXELS_API_KEY), searched with Gemini-chosen keywords, dimmed and cropped to 9:16. If the search, download, or render fails the clip falls back to an animated gradient backplate, so a slot never dies.
Notes & risks
- Copyright: only repost content you have rights to. Clipping public channels without permission can violate YouTube ToS / creators' rights. Prefer channels that allow embedding/repurposing, or lean on the generated pipeline.
- TikTok bot detection: keep
headless: false; the human-like delays inconfig/tiktok.jsonhelp. If TikTok serves a CAPTCHA the run fails loudly. - UI changes: selectors in the TikTok uploader may break; pin versions.
- Free tier limits: the Gemini free tier handles dozens of calls/day — fine for a few daily clips.
Metadata
Release files for ttclips 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ttclips-1.0.0.tar.gz | 43.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ttclips-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 88.5 kB
Release files / ttclips-1.0.0.tar.gz
| Download URL | ttclips-1.0.0.tar.gz |
|---|---|
| Size | 43.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2d36603b029405b49111ce6e26a9485677e4a6205caf1ab23fa0698cef5ac452
|
|
BLAKE2b-256 checksum How to use checksums |
10d13c1bdde1280f2e66b1ed028c446da2ed9632758a05c4bececbe0d6ec34b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / ttclips-1.0.0-py3-none-any.whl
| Download URL | ttclips-1.0.0-py3-none-any.whl |
|---|---|
| Size | 45.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ceda14dbd5858de3ccc58593d0b09ec3b12dd324ee624b62255207047e0aa173
|
|
BLAKE2b-256 checksum How to use checksums |
bab85341b7deb7ad1d255e3dcf80646412c08175c5e96598b929cdc19ec47921
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency log