Skip to content

Repository files navigation

TechShorts AI

TechShorts AI turns a technology topic into a consistent 10–12 second vertical video. It researches the topic, selects a hook, writes and reviews the script, generates narration, synchronizes captions to the spoken words, renders the video with Remotion, validates the finished MP4, and then exports or publishes it on schedule.

The system starts in OFFLINE mode. Follow the local setup first and produce one offline export before connecting any social account.

Contents

How the system works

flowchart LR
    A[Topic or calendar slot] --> B[Research]
    B --> C[Hook ranking]
    C --> D[Script and quality gate]
    D --> E[Voice generation]
    E --> F[Word timestamp alignment]
    F --> G[Remotion render]
    G --> H[MP4 media QA]
    H --> I[SEO metadata]
    I --> J{Publishing mode}
    J -->|Offline| K[Export folder and ZIP]
    J -->|Approval required| L[Wait for approval]
    L --> M[YouTube and TikTok]
    J -->|Automatic| M
Loading

The visual system comes from STYLE BIBLE.txt, Style Frame.png, and Grid Plate.png. Those files are required at the project root.

The application has two long-running processes:

  • serve runs the FastAPI dashboard and API.
  • worker claims jobs and advances them through the production pipeline.

Both must be running for the dashboard to create and process videos.

Local Windows setup

Step 1: Verify the required software

Install:

  • Python 3.12
  • Node.js 22 or newer with npm
  • FFmpeg and FFprobe
  • Git, if the project is being cloned

Open PowerShell in the project directory and verify each executable:

py -3.12 --version
node --version
npm --version
ffmpeg -version
ffprobe -version

If FFmpeg was installed while PowerShell was open, close and reopen the terminal before checking again.

Step 2: Create the Python environment

From the project root:

py -3.12 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"

If PowerShell blocks activation, the commands can be run without activating the environment:

.\.venv\Scripts\python.exe -m pip install -e ".[dev]"

Step 3: Install the video dependencies

Use the committed lockfile for a reproducible installation:

npm ci
npx remotion browser ensure
npm run video:check

The browser download happens once. Remotion uses it to render frames without opening a visible browser window.

Step 4: Create the application configuration

Copy the example environment file:

Copy-Item .env.example .env
notepad .env

At minimum, configure these values:

APP_ENV=development
APP_URL=http://localhost:8000
APP_TIMEZONE=America/Phoenix
APP_ENCRYPTION_KEY=paste-generated-fernet-key-here
OPENAI_API_KEY=paste-openai-api-key-here
PUBLISHING_MODE=OFFLINE

Generate APP_ENCRYPTION_KEY with:

python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

Copy the entire output into .env. Do not add quotes or reuse a key from another application.

Create an OpenAI API key at OpenAI API keys. The OpenAI API project must also have active billing. If needed, follow the official prepaid billing setup. A ChatGPT subscription does not configure this API project for the application.

Never commit .env. It is already excluded by .gitignore.

Step 5: Initialize the database and check the installation

python -m techshorts init-db
python -m techshorts doctor

A ready local installation reports OK for:

  • Style Bible
  • Style Frame
  • Grid Plate
  • OpenAI key configuration
  • FFmpeg and FFprobe
  • Node.js
  • Remotion

doctor verifies that a key is configured; it does not make a paid OpenAI request or verify the account balance.

Step 6: Start the dashboard

In the first PowerShell terminal:

.\.venv\Scripts\Activate.ps1
python -m techshorts serve --host 127.0.0.1 --port 8000

Step 7: Start the worker

Open a second PowerShell terminal in the same project directory:

.\.venv\Scripts\Activate.ps1
python -m techshorts worker

Keep both terminals running. Open http://localhost:8000 and confirm:

  • OpenAI says Configured.
  • Worker says Online.
  • FFmpeg says Ready.
  • The publishing banner says OFFLINE.

The development API reference is available at http://localhost:8000/api/docs and the health check is http://localhost:8000/health.

Create your first offline video

Complete this workflow before configuring social publishing.

  1. Confirm the operating mode is Offline / export.
  2. Select New production.
  3. Enter a specific topic, such as Why the first website had no images.
  4. Select the closest category.
  5. Leave Schedule empty for an immediate run.
  6. Select Queue production.
  7. Watch the job move through Research, Hook, Script, Review, Rendering, SEO, and Exported.
  8. Select the arrow beside the job to inspect its hook, script, research, sources, metadata, errors, and final video.
  9. Select Download package after the status becomes EXPORTED.

A full 1080×1920 render is CPU-intensive. The validation machine took about six minutes for the Remotion render alone. Research, voice generation, and API response time add to that total.

The completed export is also written beneath:

exports/YYYY-MM-DD/<time>_<topic>/

The export contains:

  • video.mp4
  • metadata.json
  • youtube.txt
  • tiktok.txt
  • sources.json
  • checksums.sha256.json
  • a ZIP containing the complete package

Use the dashboard

Production queue

Use the All, Active, and Failed filters to find jobs. Select the arrow on a row to open its production record.

Available actions depend on the job state:

Action Purpose
Approve Releases a ready job when APPROVAL_REQUIRED is active
Pause Stops the worker from advancing the job
Resume Returns a paused job to its previous stage
Retry Restarts a failed job from its recorded resume stage
Export ZIP Builds a local package from an already rendered video
Download package Downloads an existing export ZIP
Cancel Permanently stops unfinished pipeline work for that job

Script and metadata edits are available through the development API at /api/docs. Changing a script invalidates the previous narration, word timing, SEO, and render so the worker cannot publish stale media.

What the worker does automatically

For each job, the worker:

  1. Researches the topic and records retrieved sources.
  2. Generates six to ten hooks and scores every candidate.
  3. Writes a narration-length script.
  4. Runs the quality gate.
  5. Generates speech.
  6. Speeds up narration that slightly exceeds the video window.
  7. Transcribes the exact generated audio with word timestamps.
  8. Groups the word timestamps into readable caption cues.
  9. Renders audio, captions, motion, and category graphics together in Remotion.
  10. Rejects media without the required dimensions, duration, audio, video, or MP4 container.
  11. Generates separate YouTube and TikTok metadata.
  12. Exports or publishes according to the active mode.

Build a content schedule

  1. Select Build schedule.
  2. Choose the first local production date.
  3. Enter the number of days.
  4. Enter the number of posts per day.
  5. Enter one 24-hour posting time for each daily post, separated by commas.
  6. Select the categories that should rotate through the schedule.
  7. Optionally enter one topic seed per line. Empty slots receive AI-generated topics.
  8. Select Create schedule.

Posting windows use APP_TIMEZONE. The application converts them to UTC before storing them. For two posts per day, a valid value is:

09:30, 17:30

If the system is in offline mode, scheduled jobs wait until their due time and then create export packages. They do not contact either platform.

Publishing modes

Mode Behavior Recommended use
OFFLINE Technically blocks platform publishers and creates local exports Setup, testing, and review
APPROVAL_REQUIRED Produces the video, then waits for Approve before dispatch First production publishing mode
AUTOMATIC Dispatches validated jobs when they become due Only after successful approval-mode tests

The selected mode is persisted in the database. After the first selection, changing PUBLISHING_MODE in .env does not override the dashboard selection.

Important: The current delivery operation publishes to both YouTube and TikTok. Connect and verify both accounts before selecting APPROVAL_REQUIRED or AUTOMATIC. A single-platform publishing switch is not currently implemented.

Connect YouTube

YouTube can be tested with a localhost callback. Use the exact redirect URI shown below unless the dashboard is deployed at a public origin.

Step 1: Create the Google project

  1. Open the Google Cloud Console.
  2. Create or select a project.
  3. Open the YouTube Data API v3 library.
  4. Select Enable.

Step 2: Configure OAuth

  1. Configure the project's OAuth consent screen.

  2. If the app is in testing mode, add the Google account that owns the target YouTube channel as a test user.

  3. Open APIs & Services → Credentials.

  4. Create an OAuth client ID with application type Web application.

  5. Add this authorized redirect URI for local use:

    http://localhost:8000/oauth/youtube/callback
    
  6. Copy the client ID and client secret.

Google's official server-side OAuth instructions are available in the YouTube OAuth guide.

Step 3: Add the credentials

Update .env:

YOUTUBE_CLIENT_ID=your-google-client-id
YOUTUBE_CLIENT_SECRET=your-google-client-secret
YOUTUBE_REDIRECT_URI=http://localhost:8000/oauth/youtube/callback
YOUTUBE_CATEGORY_ID=28
YOUTUBE_PRIVACY_STATUS=private

Restart both the dashboard and worker after changing .env.

Step 4: Authorize the channel

  1. Open the dashboard.
  2. Select Connect YouTube under Systems.
  3. Sign in with the account that owns the intended channel.
  4. Grant the youtube.upload permission.
  5. Confirm the dashboard now reports YouTube — Connected.

The application requests only https://www.googleapis.com/auth/youtube.upload. Scheduled uploads are created as private and receive a publishAt time. Public applications may require Google OAuth verification.

Connect TikTok

TikTok Web Login requires a registered HTTPS callback. Complete the public HTTPS deployment before connecting TikTok. The local http://localhost callback should not be used for a TikTok Web application.

Step 1: Create and configure the TikTok app

  1. Sign in to TikTok for Developers.
  2. Create an app from Manage apps.
  3. Add Login Kit.
  4. Add the Content Posting API.
  5. Configure the Web platform and verify the required website, privacy-policy, and terms URLs.
  6. Request and obtain access to the user.info.basic and video.publish scopes.
  7. Complete TikTok's sandbox or app-review requirements for Direct Post.

See TikTok's official app creation, Login Kit, and Content Posting API instructions.

Step 2: Register the callback

For a deployment at https://shorts.example.com, register this exact URI in TikTok:

https://shorts.example.com/oauth/tiktok/callback

The URI must be HTTPS, absolute, static, and free of query parameters or fragments.

Step 3: Add the credentials

Update the deployed .env:

TIKTOK_CLIENT_KEY=your-tiktok-client-key
TIKTOK_CLIENT_SECRET=your-tiktok-client-secret
TIKTOK_REDIRECT_URI=https://shorts.example.com/oauth/tiktok/callback
TIKTOK_PRIVACY_LEVEL=SELF_ONLY
TIKTOK_AUTO_PUBLISH_ACKNOWLEDGED=false

Restart the deployment, open the dashboard, and select Connect TikTok. Complete the consent flow and confirm the dashboard reports TikTok — Connected.

The application queries the creator's allowed privacy levels before posting and rejects an unsupported TIKTOK_PRIVACY_LEVEL. It marks posted content as AI-generated.

Only after TikTok has approved the application and you intentionally accept automatic Direct Post behavior should you set:

TIKTOK_AUTO_PUBLISH_ACKNOWLEDGED=true

Restart the worker after changing this value.

Enable publishing

Do not begin with automatic mode.

  1. Confirm OpenAI, YouTube, TikTok, Worker, and FFmpeg all show healthy or connected.
  2. Confirm TIKTOK_AUTO_PUBLISH_ACKNOWLEDGED=true on the worker.
  3. Change Operating mode to Approval required.
  4. Create one unscheduled test production.
  5. Wait until the job is READY.
  6. Inspect the video, script, sources, YouTube metadata, and TikTok caption.
  7. Select Approve.
  8. Monitor the job until it becomes PUBLISHED.
  9. Verify the result on both platforms.
  10. Repeat with a future schedule before considering Automatic mode.

Publishing attempts use per-job/per-platform idempotency keys. If YouTube succeeds but TikTok fails, retrying the job does not intentionally create a second YouTube upload.

Deploy with Docker

The included Docker Compose configuration runs one web container and one worker container with shared persistent volumes. This is the recommended deployment shape for the current SQLite architecture.

Step 1: Prepare the server

Use a Linux server or VM with:

  • Docker Engine and the Docker Compose plugin
  • At least 4 GB of RAM; more CPU cores improve rendering time
  • Persistent disk space for the database, narration, videos, and exports
  • A domain name pointing to the server
  • An HTTPS reverse proxy such as Caddy, Nginx, or a managed load balancer

Use Docker's official Compose installation instructions if Compose is not available.

Verify the server:

docker --version
docker compose version

Step 2: Copy the project

Clone the repository or securely copy the project directory to the server, then change into it:

git clone <repository-url> techshorts
cd techshorts

The three brand assets must exist at the project root before building the image.

Step 3: Create the production environment file

cp .env.example .env
docker run --rm python:3.12-slim python -c "import base64,secrets; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())"

Edit .env and set at least:

APP_ENV=production
APP_URL=https://shorts.example.com
APP_TIMEZONE=America/Phoenix
APP_ENCRYPTION_KEY=paste-generated-fernet-key-here
DASHBOARD_USERNAME=choose-a-non-default-username
DASHBOARD_PASSWORD=use-a-long-random-password

DATABASE_URL=sqlite:///./data/techshorts.db
DATA_DIR=./data
MEDIA_DIR=./media
EXPORT_DIR=./exports
PUBLISHING_MODE=OFFLINE

OPENAI_API_KEY=paste-openai-api-key-here

YOUTUBE_REDIRECT_URI=https://shorts.example.com/oauth/youtube/callback
TIKTOK_REDIRECT_URI=https://shorts.example.com/oauth/tiktok/callback

Also add the platform credentials described above. Keep PUBLISHING_MODE=OFFLINE and TIKTOK_AUTO_PUBLISH_ACKNOWLEDGED=false for the first deployment.

Production startup refuses to run without dashboard credentials and APP_ENCRYPTION_KEY.

Step 4: Build and start the containers

docker compose config --quiet
docker compose up -d --build
docker compose ps

The first build downloads Python packages, npm packages, and Remotion's headless browser. It can take several minutes.

Watch startup logs:

docker compose logs -f web
docker compose logs -f worker

Stop following logs with Ctrl+C; this does not stop the containers.

Step 5: Configure HTTPS

The application listens on port 8000 and does not terminate TLS itself. Put it behind a reverse proxy before adding production OAuth callbacks.

A minimal host-installed Caddy configuration is:

shorts.example.com {
    reverse_proxy 127.0.0.1:8000
}

Configure the server firewall so port 8000 is not exposed publicly once the reverse proxy is active. Public traffic should use ports 80 and 443 through the proxy.

Step 6: Verify the deployment

curl https://shorts.example.com/health
docker compose ps

Expected health output includes:

{"status":"ok","database":true,"ffmpeg":true,"ffprobe":true,"node":true,"remotion":true}

Open https://shorts.example.com. The browser should request the production dashboard username and password.

Step 7: Register production OAuth callbacks

Register these exact callback URIs with the platform applications:

https://shorts.example.com/oauth/youtube/callback
https://shorts.example.com/oauth/tiktok/callback

Update the corresponding .env values, restart the services, and use the dashboard connection buttons:

docker compose up -d --force-recreate web worker

Step 8: Run the offline acceptance test

Create and download one offline video from the production deployment. Check its voice, caption timing, category graphics, metadata, and ZIP before enabling platform publishing.

Operate and update a deployment

View status and logs

docker compose ps
docker compose logs --tail=200 web
docker compose logs --tail=200 worker
docker compose logs -f worker

Restart services

docker compose restart web worker

Apply an update

git pull
docker compose up -d --build
docker compose ps

The application runs Alembic migrations at service startup.

Stop the deployment

docker compose down

This stops containers but preserves the named volumes. Do not add --volumes unless permanent deletion of the database, media, OAuth credentials, and exports is intended.

Back up the deployment

Back up all four named volumes shown by:

docker volume ls

The volumes contain:

  • SQLite database and encrypted OAuth records
  • generated and final media
  • offline exports
  • temporary Remotion narration assets

Stop the worker or take a provider-level consistent volume snapshot while backing up the SQLite database. Keep the matching APP_ENCRYPTION_KEY in a separate secret manager; OAuth records cannot be decrypted without it.

Scaling limitation

SQLite is appropriate for the included one-web/one-worker deployment on a single host. Do not add parallel worker replicas while using SQLite. Move DATABASE_URL to a properly supported external database before horizontal scaling; that migration is not included in this version.

Configuration reference

.env.example is the canonical list of settings.

Core settings

Setting Required Purpose
APP_ENV Yes development or production
APP_URL Yes Canonical public origin; keep the explicit OAuth redirect settings aligned with it
APP_TIMEZONE Yes IANA timezone used for calendar input
APP_ENCRYPTION_KEY OAuth/production Fernet key for stored OAuth credentials
DASHBOARD_USERNAME Production Enables dashboard HTTP Basic authentication
DASHBOARD_PASSWORD Production Password paired with the dashboard username
DATABASE_URL Yes SQLAlchemy database URL
PUBLISHING_MODE Yes Initial mode before a database value is persisted

OpenAI settings

Setting Default Purpose
OPENAI_API_KEY Empty Research, writing, review, voice, and transcription authorization
OPENAI_UTILITY_MODEL See .env.example Research and utility workload
OPENAI_CREATIVE_MODEL See .env.example Hooks, scripts, and creative workload
OPENAI_REVIEW_MODEL See .env.example Quality-gate workload
OPENAI_TTS_MODEL tts-1 Narration synthesis
OPENAI_TTS_VOICE onyx Narration voice
OPENAI_TRANSCRIPTION_MODEL whisper-1 Word timestamp alignment

If the configured account cannot access a model, replace that model value with one available to the OpenAI project and restart the services.

Media settings

Setting Default Purpose
VIDEO_WIDTH 1080 Output width
VIDEO_HEIGHT 1920 Output height
VIDEO_DURATION_SECONDS 12 Composition duration; accepted range is 10–20
VIDEO_DURATION_TOLERANCE_SECONDS 0.35 Maximum duration deviation during QA
NARRATION_END_PADDING_SECONDS 0.25 Silence reserved after speech
FFMPEG_BINARY ffmpeg FFmpeg executable or full path
FFPROBE_BINARY ffprobe FFprobe executable or full path
NODE_BINARY node Node.js executable or full path

Video development

Open the Remotion editor:

npm run video:studio

Type-check the composition:

npm run video:check

Render the bundled silent-preview properties:

node video/render.mjs video/preview-props.json media/preview.mp4

Production jobs do not use evenly distributed preview timing. They synthesize narration first and use timestamps transcribed from that exact audio file.

Quality checks

Run before deploying code changes:

python -m ruff check techshorts tests
python -m pytest
npm run video:check
python -m pip check

The automated tests cover configuration guards, state transitions, duplicate topics, retry leases, persistent publishing modes, offline blocking, platform request mapping, calendar idempotency, word-to-caption cues, export integrity, dashboard APIs, and security headers.

Troubleshooting

billing_not_active or NARRATION_API_ERROR

The OpenAI API project does not have active billing or usable credits. Add billing in the OpenAI Platform, wait a few minutes for the account to update, and retry the failed job from the dashboard.

Worker shows Offline

Confirm the worker terminal or container is running:

python -m techshorts worker

Docker:

docker compose ps
docker compose logs --tail=200 worker

FFmpeg or FFprobe is missing

Reopen the terminal after installing FFmpeg, or set the full executable paths:

FFMPEG_BINARY=C:/path/to/ffmpeg.exe
FFPROBE_BINARY=C:/path/to/ffprobe.exe

Then run python -m techshorts doctor again.

Remotion browser is missing

npx remotion browser ensure

If Windows reports spawn EPERM, run the render from a normal local PowerShell session and check whether security software is blocking node_modules/.remotion or esbuild child processes.

Rendering appears stuck

A 360-frame 1080×1920 render can use all available CPU for several minutes. Check Task Manager or docker stats; active Chromium CPU usage normally means rendering is progressing.

OAuth redirect mismatch

The URI in .env must match the platform console exactly, including scheme, hostname, port, path, and trailing slash behavior. Restart the web service after changing it.

TikTok refuses the localhost callback

TikTok Web Login requires HTTPS. Deploy the application behind HTTPS and register the public /oauth/tiktok/callback URI.

TikTok privacy mismatch

The selected TIKTOK_PRIVACY_LEVEL is not permitted for the connected creator. Change it to one returned by TikTok's creator-information endpoint, restart the worker, and retry.

A topic is rejected as a duplicate

The topic is too similar to recent history. Use a materially different angle. Explicit series labels such as Part 1 and Part 2 are allowed as separate jobs.

Port 8000 is already in use

Start the local server on another port and update APP_URL plus OAuth callbacks:

python -m techshorts serve --host 127.0.0.1 --port 8010

A failed job will not retry automatically

Non-retryable errors such as missing billing, invalid credentials, failed quality gates, or unsupported privacy settings require operator action. Correct the configuration, restart affected services, and select Retry on the failed job.

Security and maintenance

  • Keep .env, OAuth client secrets, API keys, and APP_ENCRYPTION_KEY out of source control.
  • Use HTTPS before exposing the dashboard or OAuth callbacks publicly.
  • Keep OFFLINE mode active until both social accounts are connected and tested.
  • Use a unique, long dashboard password in production.
  • Preserve APP_ENCRYPTION_KEY across deployments and restores.
  • Review platform permissions and disconnect unused OAuth applications.
  • Review this README whenever the pipeline, dashboard controls, environment variables, OAuth scopes, or deployment topology changes.

No YouTube or TikTok post is made during installation or testing. Connecting an account stores authorization, but publishing still requires a non-offline mode and, in approval mode, an explicit operator approval.

Releases

Packages

Contributors

Languages