# ClippyMe Docker Compose # # CPU / Apple Silicon / ARM (default): # docker compose up --build # # NVIDIA GPU: # docker compose -f docker-compose.yml -f docker-compose.gpu.yml up --build name: clippyme networks: clippyme-net: driver: bridge services: backend: build: context: . args: GPU_RUNTIME: ${GPU_RUNTIME:-cpu} # Set ENABLE_WHISPER_DIARIZE=1 in your shell/.env to bake # pyannote.audio into the image and enable speaker diarization on # the Faster-Whisper fallback path. Adds ~500 MB to the image. ENABLE_WHISPER_DIARIZE: ${ENABLE_WHISPER_DIARIZE:-0} container_name: clippyme-backend image: clippyme-backend ports: # Bound to loopback: security.py treats every RFC1918 peer as a trusted # client for the config/state endpoints (key overwrite, cookie download, # job deletion), so publishing on 0.0.0.0 hands that trust to the whole # LAN. Override CLIPPYME_BIND (e.g. CLIPPYME_BIND=0.0.0.0) ONLY on a # network where every device is trusted. - "${CLIPPYME_BIND:-127.0.0.1}:8000:8000" dns: - 8.8.8.8 - 8.8.4.4 environment: # HuggingFace token — required for pyannote diarization + Whisper # model downloads without rate limits. Both names are accepted. - HF_TOKEN=${HF_TOKEN:-} - HUGGINGFACE_TOKEN=${HUGGINGFACE_TOKEN:-${HF_TOKEN:-}} # ASR provider + credentials. TRANSCRIPTION_PROVIDER=deepgram|elevenlabs|whisper. - TRANSCRIPTION_PROVIDER=${TRANSCRIPTION_PROVIDER:-deepgram} - DEEPGRAM_API_KEY=${DEEPGRAM_API_KEY:-} - DEEPGRAM_MODEL=${DEEPGRAM_MODEL:-nova-3} # Default 'multi' enables Nova-3 native EN+IT code-switching. # Users can override per-job from the Create tab dropdown. - DEEPGRAM_LANGUAGE=${DEEPGRAM_LANGUAGE:-multi} - DEEPGRAM_DIARIZE=${DEEPGRAM_DIARIZE:-true} # ElevenLabs Scribe (3rd STT provider). scribe_v2 supports NO_VERBATIM # filler-strip. AUDIO_ISOLATION=1 adds an opt-in pre-ASR noise/music # removal pass (provider-agnostic; needs the ElevenLabs key). - ELEVENLABS_API_KEY=${ELEVENLABS_API_KEY:-} - ELEVENLABS_MODEL=${ELEVENLABS_MODEL:-scribe_v1} - ELEVENLABS_LANGUAGE=${ELEVENLABS_LANGUAGE:-multi} - ELEVENLABS_DIARIZE=${ELEVENLABS_DIARIZE:-true} - ELEVENLABS_NO_VERBATIM=${ELEVENLABS_NO_VERBATIM:-false} - ELEVENLABS_AUDIO_ISOLATION=${ELEVENLABS_AUDIO_ISOLATION:-false} # Whisper fallback diarization. Requires ENABLE_WHISPER_DIARIZE=1 # at build time AND HF_TOKEN set at runtime. - WHISPER_DIARIZE=${WHISPER_DIARIZE:-true} # Gemini key is normally passed per-request via the X-Gemini-Key # header, but can also be set here for CLI runs that shell into the # container directly. - GEMINI_API_KEY=${GEMINI_API_KEY:-} - GEMINI_MODEL=${GEMINI_MODEL:-gemini-3.5-flash} # yt-dlp hardening. NOCHECKCERT=1 disables SSL verification only in # trusted sandboxes; THROTTLED_RATE is the bytes/sec floor below # which yt-dlp re-fetches a slow segment (default 102400 = 100 KB/s). - YTDLP_NOCHECKCERT=${YTDLP_NOCHECKCERT:-} - YTDLP_THROTTLED_RATE=${YTDLP_THROTTLED_RATE:-} # Auto-purge of output/ and uploads/ folders. 0 = disabled # (recommended — the History tab's Delete button is the single # source of truth). Default fallback in app.py is 30 days. - JOB_RETENTION_SECONDS=${JOB_RETENTION_SECONDS:-0} # Reframe camera tuning (all optional; defaults preserve current feel). # REFRAME_SMOOTHER=euro switches the active-speaker camera to the 1€ # adaptive filter; leave blank for the proven two-speed EMA. The LOST_* # knobs control lost-subject drift-to-center (always on). EURO_* tune the # 1€ filter when enabled. See docs/architecture/reframe.md. - REFRAME_SMOOTHER=${REFRAME_SMOOTHER:-} - REFRAME_LOST_HOLD=${REFRAME_LOST_HOLD:-90} - REFRAME_LOST_DRIFT=${REFRAME_LOST_DRIFT:-0.05} - REFRAME_EURO_MINCUTOFF=${REFRAME_EURO_MINCUTOFF:-0.014} - REFRAME_EURO_BETA=${REFRAME_EURO_BETA:-0.0008} # Comfort mode (anti-nausea, DEFAULT ON): biases the camera toward a locked # crop + fixed per-scene zoom (the research-backed fix for seasickness from # continuous tracking). Costs a second decode pass. Set REFRAME_COMFORT=0 # to revert to the original single-pass streaming tracker. STATIONARY_THRESH # / ZOOM_LOCK override the comfort defaults (0.30 / on) when set explicitly. - REFRAME_COMFORT=${REFRAME_COMFORT:-1} - REFRAME_STATIONARY_THRESH=${REFRAME_STATIONARY_THRESH:-} - REFRAME_ZOOM_LOCK=${REFRAME_ZOOM_LOCK:-} - REFRAME_SNAP_CENTER=${REFRAME_SNAP_CENTER:-0.10} # AUTO static-framing policy (DEFAULT ON): collapse each scene to ONE fixed # crop so the camera is a locked tripod within every shot — zero pan, zero # mid-shot zoom. TRACK locks on the static face; a single subject that # MOVES more than MOTION_WIDE_THRESH (fraction of frame) is demoted to WIDE # (locked, zoomed-out) instead of being chased; 2+ faces also → WIDE. Set # REFRAME_STATIC_AUTO=0 to fall back to the per-frame eased smoother. - REFRAME_STATIC_AUTO=${REFRAME_STATIC_AUTO:-} - REFRAME_MOTION_WIDE_THRESH=${REFRAME_MOTION_WIDE_THRESH:-0.12} # Opt-in: content-aware crop for faceless (GENERAL) scenes instead of # letterboxing (Sobel saliency). Default off = byte-identical letterbox. - REFRAME_SALIENT_GENERAL=${REFRAME_SALIENT_GENERAL:-} # Opt-in: in faceless (GENERAL) scenes, follow a weighted-object centroid # (product/dog/car) by reusing the existing YOLO pass — no 2nd network. # `1` = curated COCO defaults; `dog:3,car:2,bottle:1.5` = custom weights. # Takes precedence over SALIENT_GENERAL; default off = byte-identical. - REFRAME_OBJECT_WEIGHTS=${REFRAME_OBJECT_WEIGHTS:-} # FrameShift face-first `object` reframe mode weights (defaults mirror the # FrameShift GUI: face 1.0, person 0.8, default 0.5). Override per class, # e.g. `face:1,person:0.8,default:0.5,dog:3`. Empty = GUI defaults. - REFRAME_FRAMESHIFT_WEIGHTS=${REFRAME_FRAMESHIFT_WEIGHTS:-} # Encode quality (shared by every libx264 render/compose pass — reframe, # zoom, grade, subtitles, smart-cut, hook, logo). CRF 18 / preset medium is # near-visually-lossless so the stacked re-encodes don't compound into soft # output. Lower CRF = higher quality + bigger files (0-51); a faster preset # (e.g. `fast`/`veryfast`) trades a little quality/size for render speed. - CLIPPYME_X264_CRF=${CLIPPYME_X264_CRF:-18} - CLIPPYME_X264_PRESET=${CLIPPYME_X264_PRESET:-medium} # Transcription: strip to audio-only (FLAC) before sending to the provider. # DEFAULT ON; set to false to upload/decode the full video. - CLIPPYME_TRANSCRIBE_AUDIO_ONLY=${CLIPPYME_TRANSCRIBE_AUDIO_ONLY:-true} # Optional shared-secret auth for /api. Unset (default) = no auth beyond # the private-network trust model. Set together with CLIPPYME_BIND=0.0.0.0 # for a deliberate LAN deployment: every API request must then carry # X-API-Token (the dashboard has a Settings field for it). - CLIPPYME_API_TOKEN=${CLIPPYME_API_TOKEN:-} volumes: - .:/app - /app/__pycache__ - ./data:/app/data networks: - clippyme-net restart: unless-stopped frontend: build: ./dashboard container_name: clippyme-frontend image: clippyme-frontend ports: # Loopback too: the Vite dev proxy forwards /api to the backend from # INSIDE the docker network, where the frontend container is itself a # trusted private peer — so a LAN-exposed dashboard would reopen the # exact hole the backend loopback binding closes. Same CLIPPYME_BIND # override applies. - "${CLIPPYME_BIND:-127.0.0.1}:5175:5175" volumes: - ./dashboard:/app - /app/node_modules networks: - clippyme-net restart: unless-stopped depends_on: - backend