Watcher Architecture
One card in, posts out. Visual map of every part and every flow.
1. System overview
The browser talks to one Flask process. That process renders UI, runs OAuth, and spawns one background thread per post job.
2. File map
Each file has one job. This is the whole project.
| File | Responsibility | Kind |
|---|---|---|
app.py | Routes, sessions, render templates, start jobs, OAuth endpoints | entry |
jobs.py | Create job, background pipeline, retry one channel, status and logs | orchestrator |
media.py | Download, detect kind, mix music, force H.264 yuv420p | media |
channels.py | Facebook, Instagram, TikTok posting and secret loading | external |
facebook_auth.py | OAuth URL, token exchange, page list, page select | auth |
templates/ | index.html, pages.html, job.html, callback.html | view |
data/jobs.json | All jobs, per-channel results, logs | state |
config.json | Page, page token, Instagram id (env vars override) | secrets |
3. Posting flow
From the one card to a final status. The heavy work runs in a background thread so the page stays responsive.
4. Media pipeline
Music is video only. A mix failure stops the whole job. Retry reuses the processed files, so ffmpeg does not run twice.
| Rule | Behavior |
|---|---|
| File vs link | File wins when both are set |
| Music | Applied to video only; ignored for images |
| Mix failure | Job posts nothing |
| Codec | Video always normalized to H.264 yuv420p mp4 |
5. Facebook login flow
The user never types an App ID, App Secret, Page ID, or token. The App ID is fixed, the page token is captured automatically.
6. Channels
Each channel has its own upload and completion rule.
7. State and storage
Secrets and job state are plain JSON on disk. Environment variables override file values.
8. Deployment
Gunicorn runs one worker with threads, so the in-process background jobs keep working. Public HTTPS is required for Facebook login.