Skip to main content
Glama

Estúdio

English · Português

A web app for AI video pre-production and production — series, films and spots: visual bible, story and scripts, assets (characters, locations, props) with generated sheets, 3D sets of the locations, storyboard, staging and cameras, shot-by-shot video generation, editing, a Writers' room shared with AI agents (MCP server) and a work agenda. The interface is in European Portuguese or English (Settings → Language).

Stack: FastAPI + SQLModel/SQLite + Alembic + Jinja2/HTMX (idiomorph) + three.js, in a Docker container. A single user (the owner), with email and password login.

Layout

app/             code (routes, models, templates, static files, MCP server)
design/          UI design (HANDOFF.md, reference HTML and PNG screens)
docs/            architecture, prompt rules and decision log (in Portuguese)
migrations/      Alembic migrations (run automatically on startup)
deploy/nginx/    nginx site template for a server with HTTPS
scripts/         deploy.sh (publish with the generation queue empty) and backup.sh
tests/           tests (python -m unittest)
data/            volume: database and files — not in the repository
.env             secrets — not in the repository (template in .env.example)

Related MCP server: Idea to Motion

Requirements

  • Docker with the Compose plugin (Docker Desktop on Mac/Windows, or Docker Engine on Linux), on an x86-64 processor (Intel/AMD); ARM works under emulation (see "local network").

  • ~4 GB of disk for the image (it includes headless Chromium, used for 3D set renders and PDFs).

  • To generate images and videos: accounts with the providers you want to use (see "AI providers"). Without any, the app still works to organize the work, write, and upload files made elsewhere.

Configuration (.env)

cp .env.example .env && chmod 600 .env

Variable

What for

SECRET_KEY

signs the session and encrypts the stored API keys. Generate: python3 -c "import secrets; print(secrets.token_urlsafe(48))". Don't change it later (stored keys would become unreadable).

ADMIN_EMAIL

the email you log in with (the only user).

ADMIN_INITIAL_PASSWORD_HASH

argon2 hash of the initial password, in single quotes (it contains $). Generate: docker compose run --rm --no-deps app python -m app.cli hash-password. You change the password on first login.

COOKIE_SECURE

true with HTTPS (server); false on http://localhost.

PUBLIC_URL

the address where the app is opened (e.g. https://estudio.example.com or http://localhost:8010). The MCP connector and its OAuth login use it.

AUTHOR_NAME

your name in the Writers' room (e.g. Ana); "Autor" by default.

CLAUDE_CODE_OAUTH_TOKEN

optional: connects the Assistant and the prompt writer to Claude (see below). Alternatively ANTHROPIC_API_KEY.

Install on a local machine (Mac, Windows, Linux)

git clone <this repository> estudio && cd estudio
cp .env.example .env
# in .env: SECRET_KEY, ADMIN_EMAIL, COOKIE_SECURE=false, PUBLIC_URL=http://localhost:8010
docker compose build
docker compose run --rm --no-deps app python -m app.cli hash-password   # type the password; paste the hash into .env
mkdir -p data
docker compose up -d

Open http://localhost:8010 and log in with ADMIN_EMAIL and the initial password. On startup the app creates the database and applies the migrations and the initial lists. Data lives in ./data (SQLite database and media files).

On Linux, if the container can't write to ./data: sudo chown -R 10001:10001 data (the container runs as an unprivileged user, uid 10001).

The MCP connector only works with Claude/ChatGPT on the web if the app is reachable from the internet over HTTPS (see below). Locally you can use it from MCP clients on the same machine (e.g. Claude Code) with PUBLIC_URL=http://localhost:8010.

Install on a local-network server (mini PC, NAS, always-on computer)

To use the app from several computers, tablets or phones on the same network, without exposing it to the internet:

  1. On the computer that will serve the app, install Docker and follow the local install above, with these lines in .env:

    BIND_ADDRESS=0.0.0.0                  # accept connections from the whole local network (not only this computer)
    PORT=8010
    PUBLIC_URL=http://192.168.1.50:8010   # that computer's fixed IP on the network (or its name, e.g. http://estudio.local:8010)
    COOKIE_SECURE=false                   # no HTTPS on the local network
  2. Give that computer a fixed IP (on the router: "DHCP reservation") so the address doesn't change.

  3. If there's a firewall (Windows, ufw on Linux, NAS), open port 8010 to the local network only.

  4. On the other devices, open http://192.168.1.50:8010.

Notes:

  • Without HTTPS, the password and the session travel in clear text on the network: use this only on a network you own, never on public Wi-Fi, and don't forward the port on the router to the internet.

  • For HTTPS at home: a proxy such as Caddy or nginx with your own certificate, then COOKIE_SECURE=true and PUBLIC_URL=https://….

  • The Claude and ChatGPT web MCP connector needs to reach the app from the internet over HTTPS. On a local network, without opening ports, the simplest way is a tunnel (e.g. Cloudflare Tunnel or Tailscale Funnel) to an https://… address, which becomes the PUBLIC_URL (with COOKIE_SECURE=true). Without a tunnel the rest of the app works the same.

  • On ARM processors (Apple Silicon Macs, Raspberry Pi, some NAS) the image runs under x86-64 emulation: it works, but building and rendering are slower. 4 GB of RAM or more is recommended.

Install on a server (VPS with HTTPS)

  1. DNS: an A record (and AAAA if you have IPv6) for your domain, e.g. estudio.example.com, pointing at the server's IP.

  2. Code and configuration:

    git clone <this repository> /opt/estudio && cd /opt/estudio
    cp .env.example .env && chmod 600 .env
    # in .env: SECRET_KEY, ADMIN_EMAIL, COOKIE_SECURE=true, PUBLIC_URL=https://estudio.example.com, AUTHOR_NAME
    docker compose build
    docker compose run --rm --no-deps app python -m app.cli hash-password   # → ADMIN_INITIAL_PASSWORD_HASH
    mkdir -p data && chown -R 10001:10001 data && chmod 700 data
    docker compose up -d

    The app listens only on 127.0.0.1:8010; nginx is what exposes it.

  3. nginx and certificate (Let's Encrypt):

    sed 's/estudio.example.com/YOUR_DOMAIN/g' deploy/nginx/estudio.conf.example > /etc/nginx/sites-available/estudio.conf
    ln -s /etc/nginx/sites-available/estudio.conf /etc/nginx/sites-enabled/estudio.conf
    # the first time the certificate doesn't exist yet: comment out the «listen 443» block and reload nginx, then:
    certbot certonly --webroot -w /var/www/html -d YOUR_DOMAIN
    # put the 443 block back
    nginx -t && systemctl reload nginx

    nginx accepts requests up to 200 MB (video clips) and the certificate renews with certbot's timer.

  4. Daily backup (optional): scripts/backup.sh (database and media, a single file in /var/backups/director/); for example in /etc/cron.d/estudio-backup: 30 3 * * * root /opt/estudio/scripts/backup.sh >> /var/log/estudio-backup.log 2>&1.

  5. Update: git pull && ./scripts/deploy.sh (builds the image and only restarts with the generation queue empty, so paid requests aren't repeated).

AI providers

None of these is required; each one enables part of the generation.

  • Claude (Assistant, prompt writer, Artlist quotes): claude setup-token on a machine with Claude Code and your subscription; put the token in CLAUDE_CODE_OAUTH_TOKEN and run docker compose up -d.

  • Artlist (images and video): Settings → Connections → connect the account (the app uses Artlist's MCP server through Claude Code in the container). Needs the Claude token above.

  • GPT Image (ChatGPT subscription, via the Codex CLI): docker compose exec app codex login --device-auth.

  • fal.ai, BytePlus (Seedance) and others: keys in Settings → API keys (encrypted in the database).

MCP connector (Claude, ChatGPT)

The app is an MCP server at <PUBLIC_URL>/mcp, with OAuth (dynamic client registration) and your login as the authorization. In Claude: Settings → Connectors → Add custom connector → <PUBLIC_URL>/mcp. In ChatGPT: developer mode → Apps & connectors → Create, with the same address and OAuth. The tools cover projects, story and script, assets (including their reference photos: list/download, upload, remove), 3D sets, storyboard, video, audio for script lines (e.g. narration made elsewhere), downloads of everything approved for editing elsewhere, the Writers' room and the Agenda; nothing is approved without you, and paid generations ask for cost confirmation.

Interface language

Settings → Idioma · Language switches the interface between Portuguese (default) and English. The app's pages are written in Portuguese and translated on the way out by a dictionary (app/i18n/en.json, built from the code with the glossary in app/i18n/GLOSSARY.md); project content (scripts, names, notes, prompts) is never translated. The Assistant answers in the chosen language. When adding UI text, add its translation to en.json (crawling with I18N_COLLECT=<file> lists what's missing).

Tests

docker compose exec app python -m unittest discover -s tests -v

Access

  • Only the email set in ADMIN_EMAIL has access.

  • Changing the password on first login is mandatory. The new password is stored (as an argon2 hash) in the database. .env only holds the initial password.

  • To change the password later: Change password menu. All other sessions end.

  • Forgot the password: docker compose exec app python -m app.cli reset-password restores the initial password from .env, which must be changed at the next login.

  • At most 5 failed attempts per IP in 15 minutes.

Files

  • Files in data/media/<PREFIX>/: assets/<CODE>/vN.ext (sheets), assets/<CODE>/retrato/vN.ext (character portraits), assets/<CODE>/maquete/ (location renders and views), planos/<id>/clip|maquete|fotogramas/. The database only stores the path.

  • Download and zip names follow the code: PRJ_ANA_sheet.png, AVT_C01_P02_v3.mp4, and in the edit export 01_AVT_C01_P01.mp4… in shot order.

  • Videos: MP4 or MOV up to 200 MB; the duration is read from the file itself (mvhd box).

  • Accepted uploads: PNG, JPG and WEBP up to 25 MB (type checked from the content with Pillow). nginx accepts requests up to 200 MB, for video clips.

  • Rejecting a result removes it from where it was used. The record stays, marked as rejected, and can never be chosen as a reference.

Prompt rules

Prompts (sheets, portraits, views, video, cover) are written by the prompt writer, following the rules in docs/PROMPT_RULES.md and the guides in Settings → Prompts (see docs/DECISOES.md); the app doesn't assemble them. Next to each prompt the warnings from section 5 appear — none of them blocks; limits and word lists are editable in Settings → Prompt checks. Each edited prompt keeps versions that can be restored. The Assistant receives these rules whenever it proposes or fixes a prompt.

Assistant (Claude Code)

The Assistant panel calls the Claude Code CLI inside the container, as the app's unprivileged user, in non-interactive mode (claude -p): no tools (--tools ""), --restricted, no third-party MCP or settings, no saved sessions, in an empty temporary folder and with a time limit (Settings → Models and prompts → Assistant). The reply is validated JSON (--json-schema); each proposal is checked against the fields allowed on the current screen and is only saved with Apply.

Connect (once):

claude setup-token            # on the VPS, with your Claude account; copy the token it prints
nano .env                     # CLAUDE_CODE_OAUTH_TOKEN=<token>
docker compose up -d          # recreates the container with the token

Instead of the token, ANTHROPIC_API_KEY= in .env.

GPT Image (Codex CLI)

Generates and edits images (asset sheets, portraits, 3D set views of locations and the project cover) with the GPT Image of the ChatGPT subscription, through the Codex CLI installed in the container (version pinned in the Dockerfile). The login is kept in data/codex/ (outside the backup):

docker compose exec app codex login --device-auth    # open the link, enter the code

Requests go into a queue (one at a time) and the result appears in Results as "pending", with the GPT tag. "Edit with GPT" redoes an image with a change described in English. Codex runs as an unprivileged user, in a temporary folder, with a minimal environment (none of the app's secrets); its shell is blocked by the sandbox. Each image uses 3–5× more of the Codex limit than a message. Enable/disable and time limit in Settings.

Backup

scripts/backup.sh runs every day at 03:30 (/etc/cron.d/director-backup) and creates /var/backups/director/director-backup.tar.gz, with the database and data/media. There is only one backup: the new one replaces the previous one, and only once it's complete. Log: /var/log/director-backup.log.

Restore:

docker compose down
tar -xzf /var/backups/director/director-backup.tar.gz -C data/
chown -R 10001:10001 data
docker compose up -d

Operation

docker compose logs -f                 # logs
docker compose up -d --build           # update after code changes
docker compose exec app alembic revision --autogenerate -m "..."   # new migration (dev)

Migrations run with PRAGMA foreign_keys=OFF (see migrations/env.py): in SQLite, batch mode recreates tables and, with foreign keys on, the DROP would cascade-delete the child tables' data. Always run scripts/backup.sh before publishing a migration.

Look

app/static/css/estudio.css is the design CSS, copied unchanged. The app's adjustments (scroll, tablet/phone sizes, extra components) are in app/static/css/app.css. The light theme is stored in the browser (Settings → Account).

API keys

Settings → API keys stores the keys of model providers (Anthropic, OpenAI, Google, Mistral, Z.ai, Alibaba, DeepSeek, MiniMax, xAI, BytePlus, Black Forest Labs, Kling, Azure, Meta, Runway, Luma, OpenRouter, fal.ai, Replicate). They're encrypted (Fernet) in the database with a key derived from SECRET_KEY: the backup doesn't expose them, but changing SECRET_KEY makes them unreadable (the page asks you to enter them again). They're never shown after being saved. Having a key stored doesn't connect the provider to the app: each integration (Assistant, images, video) is separate.

License

Copyright (C) 2026 Leca-pt

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. The full text is in LICENSE.

In short: you can use, study, change and share the app. If you change it and run it for other people (for example, on a server reachable from the internet), you must give them access to the source code of your version, also under the AGPL-3.0.

Third-party components keep their own licenses (e.g. three.js, MIT, in app/static/vendor/three/).

Related MCP Connectors

Related MCP Servers