329 lines
13 KiB
YAML
329 lines
13 KiB
YAML
services:
|
|
podx-web:
|
|
build:
|
|
context: ./app
|
|
args:
|
|
BASE_IMAGE: ${GPU_BASE_IMAGE:-python:3.11-slim}
|
|
container_name: podx-web
|
|
env_file: [.env]
|
|
environment:
|
|
MEILI_URL: http://meili:7700
|
|
REDIS_URL: redis://redis:6379/0
|
|
LIBRARY_ROOT: /library
|
|
TRANSCRIPT_ROOT: /library
|
|
TMP_ROOT: /tmpdl
|
|
WHISPER_MODEL: large-v3
|
|
WHISPER_PRECISION: int8
|
|
WHISPER_DEVICE: ${WHISPER_DEVICE:-cpu}
|
|
WHISPER_CPU_THREADS: ${WHISPER_CPU_THREADS:-4}
|
|
TRANSCRIBE_BACKEND: ${TRANSCRIBE_BACKEND:-local}
|
|
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
|
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.openai.com/v1}
|
|
OPENAI_TRANSCRIBE_MODEL: ${OPENAI_TRANSCRIBE_MODEL:-whisper-1}
|
|
OPENAI_TRANSCRIBE_TIMEOUT: ${OPENAI_TRANSCRIBE_TIMEOUT:-600}
|
|
OPENWEBUI_AUTO_FIX_METADATA: ${OPENWEBUI_AUTO_FIX_METADATA:-1}
|
|
OPENWEBUI_METADATA_TEMPLATE_JSON: ${OPENWEBUI_METADATA_TEMPLATE_JSON:-}
|
|
MEDIA_NORMALIZE: ${MEDIA_NORMALIZE:-1}
|
|
MEDIA_NORMALIZE_KEEP_ORIGINAL: ${MEDIA_NORMALIZE_KEEP_ORIGINAL:-0}
|
|
VIDEO_NORMALIZE_CODEC: ${VIDEO_NORMALIZE_CODEC:-hevc}
|
|
VIDEO_NORMALIZE_EXTENSION: ${VIDEO_NORMALIZE_EXTENSION:-.mp4}
|
|
VIDEO_NORMALIZE_CRF: ${VIDEO_NORMALIZE_CRF:-28}
|
|
VIDEO_NORMALIZE_PRESET: ${VIDEO_NORMALIZE_PRESET:-medium}
|
|
VIDEO_NORMALIZE_TUNE: ${VIDEO_NORMALIZE_TUNE:-}
|
|
VIDEO_NORMALIZE_AUDIO_CODEC: ${VIDEO_NORMALIZE_AUDIO_CODEC:-aac}
|
|
VIDEO_NORMALIZE_AUDIO_BITRATE: ${VIDEO_NORMALIZE_AUDIO_BITRATE:-160k}
|
|
AUDIO_NORMALIZE_CODEC: ${AUDIO_NORMALIZE_CODEC:-libmp3lame}
|
|
AUDIO_NORMALIZE_EXTENSION: ${AUDIO_NORMALIZE_EXTENSION:-.mp3}
|
|
AUDIO_NORMALIZE_BITRATE: ${AUDIO_NORMALIZE_BITRATE:-192k}
|
|
AUDIO_NORMALIZE_CHANNELS: ${AUDIO_NORMALIZE_CHANNELS:-2}
|
|
OPENWEBUI_URL: ${OPENWEBUI_CONTAINER_URL:-http://open-webui:8080}
|
|
OPENWEBUI_API_KEY: ${OPENWEBUI_API_KEY}
|
|
OPENWEBUI_KB_NAME: ${OPENWEBUI_KB_NAME:-Homelab Library}
|
|
OPENWEBUI_KB_ID: ${OPENWEBUI_KB_ID:-}
|
|
volumes:
|
|
- ${TMP_HOST_DIR:-./tmp}:/tmpdl
|
|
- ${MODELS_HOST_DIR:-./models}:/root/.cache/huggingface
|
|
- ./app:/app
|
|
- /mnt/skynet-media/data/media/podx-video:/library/video
|
|
- /mnt/skynet-media/data/media/podx-audio:/library/audio
|
|
ports: ["8088:8080"]
|
|
depends_on: [podx-worker, meili, redis, open-webui]
|
|
restart: unless-stopped
|
|
extra_hosts:
|
|
- host.docker.internal:host-gateway
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/health || exit 1"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
|
|
# Main worker: handles downloads, indexing, RSS, OWUI, etc. (no heavy Whisper)
|
|
podx-worker:
|
|
build:
|
|
context: ./app
|
|
args:
|
|
BASE_IMAGE: ${GPU_BASE_IMAGE:-python:3.11-slim}
|
|
container_name: podx-worker
|
|
command: ["rq", "worker", "-u", "redis://redis:6379/0", "default"]
|
|
env_file: [.env]
|
|
environment:
|
|
MEILI_URL: http://meili:7700
|
|
REDIS_URL: redis://redis:6379/0
|
|
LIBRARY_ROOT: /library
|
|
TRANSCRIPT_ROOT: /library
|
|
TMP_ROOT: /tmpdl
|
|
WHISPER_MODEL: large-v3
|
|
WHISPER_PRECISION: int8
|
|
WHISPER_DEVICE: ${WHISPER_DEVICE:-cpu}
|
|
WHISPER_CPU_THREADS: ${WHISPER_CPU_THREADS:-4}
|
|
WHISPER_LOG_SEGMENTS: ${WHISPER_LOG_SEGMENTS:-1}
|
|
WHISPER_RESUME: ${WHISPER_RESUME:-1}
|
|
WHISPER_PARTIAL_SAVE_EVERY_SEGS: ${WHISPER_PARTIAL_SAVE_EVERY_SEGS:-20}
|
|
TRANSCRIBE_BACKEND: ${TRANSCRIBE_BACKEND:-local}
|
|
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
|
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.openai.com/v1}
|
|
OPENAI_TRANSCRIBE_MODEL: ${OPENAI_TRANSCRIBE_MODEL:-whisper-1}
|
|
OPENAI_TRANSCRIBE_TIMEOUT: ${OPENAI_TRANSCRIBE_TIMEOUT:-600}
|
|
OPENWEBUI_AUTO_FIX_METADATA: ${OPENWEBUI_AUTO_FIX_METADATA:-1}
|
|
OPENWEBUI_METADATA_TEMPLATE_JSON: ${OPENWEBUI_METADATA_TEMPLATE_JSON:-}
|
|
MEDIA_NORMALIZE: ${MEDIA_NORMALIZE:-1}
|
|
MEDIA_NORMALIZE_KEEP_ORIGINAL: ${MEDIA_NORMALIZE_KEEP_ORIGINAL:-0}
|
|
VIDEO_NORMALIZE_CODEC: ${VIDEO_NORMALIZE_CODEC:-hevc}
|
|
VIDEO_NORMALIZE_EXTENSION: ${VIDEO_NORMALIZE_EXTENSION:-.mp4}
|
|
VIDEO_NORMALIZE_CRF: ${VIDEO_NORMALIZE_CRF:-28}
|
|
VIDEO_NORMALIZE_PRESET: ${VIDEO_NORMALIZE_PRESET:-medium}
|
|
VIDEO_NORMALIZE_TUNE: ${VIDEO_NORMALIZE_TUNE:-}
|
|
VIDEO_NORMALIZE_AUDIO_CODEC: ${VIDEO_NORMALIZE_AUDIO_CODEC:-aac}
|
|
VIDEO_NORMALIZE_AUDIO_BITRATE: ${VIDEO_NORMALIZE_AUDIO_BITRATE:-160k}
|
|
AUDIO_NORMALIZE_CODEC: ${AUDIO_NORMALIZE_CODEC:-libmp3lame}
|
|
AUDIO_NORMALIZE_EXTENSION: ${AUDIO_NORMALIZE_EXTENSION:-.mp3}
|
|
AUDIO_NORMALIZE_BITRATE: ${AUDIO_NORMALIZE_BITRATE:-192k}
|
|
AUDIO_NORMALIZE_CHANNELS: ${AUDIO_NORMALIZE_CHANNELS:-2}
|
|
WORKER_MODE: all
|
|
NVIDIA_VISIBLE_DEVICES: ${NVIDIA_VISIBLE_DEVICES:-all}
|
|
NVIDIA_DRIVER_CAPABILITIES: ${NVIDIA_DRIVER_CAPABILITIES:-video,compute,utility}
|
|
OPENWEBUI_URL: ${OPENWEBUI_CONTAINER_URL:-http://open-webui:8080}
|
|
OPENWEBUI_API_KEY: ${OPENWEBUI_API_KEY}
|
|
OPENWEBUI_KB_NAME: ${OPENWEBUI_KB_NAME:-Homelab Library}
|
|
OPENWEBUI_KB_ID: ${OPENWEBUI_KB_ID:-}
|
|
PYTHONPATH: /app
|
|
JOB_TIMEOUT: ${JOB_TIMEOUT:-14400}
|
|
JOB_TTL: ${JOB_TTL:-86400}
|
|
RESULT_TTL: ${RESULT_TTL:-86400}
|
|
FAILURE_TTL: ${FAILURE_TTL:-86400}
|
|
volumes:
|
|
- ${TMP_HOST_DIR:-./tmp}:/tmpdl
|
|
- ${MODELS_HOST_DIR:-./models}:/root/.cache/huggingface
|
|
- ./app:/app
|
|
- /mnt/skynet-media/data/media/podx-video:/library/video
|
|
- /mnt/skynet-media/data/media/podx-audio:/library/audio
|
|
runtime: ${DOCKER_GPU_RUNTIME:-runc}
|
|
depends_on: [meili, redis, open-webui]
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "exit 0"]
|
|
extra_hosts:
|
|
- host.docker.internal:host-gateway
|
|
|
|
# Transcribe-only worker: listens to the "transcribe" queue and runs Whisper jobs
|
|
podx-worker-transcribe:
|
|
build:
|
|
context: ./app
|
|
args:
|
|
BASE_IMAGE: ${GPU_BASE_IMAGE:-python:3.11-slim}
|
|
container_name: podx-worker-transcribe
|
|
command: ["rq", "worker", "-u", "redis://redis:6379/0", "transcribe"]
|
|
env_file: [.env]
|
|
environment:
|
|
MEILI_URL: http://meili:7700
|
|
REDIS_URL: redis://redis:6379/0
|
|
LIBRARY_ROOT: /library
|
|
TRANSCRIPT_ROOT: /library
|
|
TMP_ROOT: /tmpdl
|
|
WHISPER_MODEL: large-v3
|
|
WHISPER_PRECISION: int8
|
|
WHISPER_DEVICE: ${WHISPER_DEVICE:-cpu}
|
|
WHISPER_CPU_THREADS: ${WHISPER_CPU_THREADS:-4}
|
|
WHISPER_LOG_SEGMENTS: ${WHISPER_LOG_SEGMENTS:-1}
|
|
WHISPER_RESUME: ${WHISPER_RESUME:-1}
|
|
WHISPER_PARTIAL_SAVE_EVERY_SEGS: ${WHISPER_PARTIAL_SAVE_EVERY_SEGS:-20}
|
|
TRANSCRIBE_BACKEND: ${TRANSCRIBE_BACKEND:-local}
|
|
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
|
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.openai.com/v1}
|
|
OPENAI_TRANSCRIBE_MODEL: ${OPENAI_TRANSCRIBE_MODEL:-whisper-1}
|
|
OPENAI_TRANSCRIBE_TIMEOUT: ${OPENAI_TRANSCRIBE_TIMEOUT:-600}
|
|
OPENWEBUI_AUTO_FIX_METADATA: ${OPENWEBUI_AUTO_FIX_METADATA:-1}
|
|
OPENWEBUI_METADATA_TEMPLATE_JSON: ${OPENWEBUI_METADATA_TEMPLATE_JSON:-}
|
|
MEDIA_NORMALIZE: ${MEDIA_NORMALIZE:-1}
|
|
MEDIA_NORMALIZE_KEEP_ORIGINAL: ${MEDIA_NORMALIZE_KEEP_ORIGINAL:-0}
|
|
VIDEO_NORMALIZE_CODEC: ${VIDEO_NORMALIZE_CODEC:-hevc}
|
|
VIDEO_NORMALIZE_EXTENSION: ${VIDEO_NORMALIZE_EXTENSION:-.mp4}
|
|
VIDEO_NORMALIZE_CRF: ${VIDEO_NORMALIZE_CRF:-28}
|
|
VIDEO_NORMALIZE_PRESET: ${VIDEO_NORMALIZE_PRESET:-medium}
|
|
VIDEO_NORMALIZE_TUNE: ${VIDEO_NORMALIZE_TUNE:-}
|
|
VIDEO_NORMALIZE_AUDIO_CODEC: ${VIDEO_NORMALIZE_AUDIO_CODEC:-aac}
|
|
VIDEO_NORMALIZE_AUDIO_BITRATE: ${VIDEO_NORMALIZE_AUDIO_BITRATE:-160k}
|
|
AUDIO_NORMALIZE_CODEC: ${AUDIO_NORMALIZE_CODEC:-libmp3lame}
|
|
AUDIO_NORMALIZE_EXTENSION: ${AUDIO_NORMALIZE_EXTENSION:-.mp3}
|
|
AUDIO_NORMALIZE_BITRATE: ${AUDIO_NORMALIZE_BITRATE:-192k}
|
|
AUDIO_NORMALIZE_CHANNELS: ${AUDIO_NORMALIZE_CHANNELS:-2}
|
|
WORKER_MODE: transcribe
|
|
NVIDIA_VISIBLE_DEVICES: ${NVIDIA_VISIBLE_DEVICES:-all}
|
|
NVIDIA_DRIVER_CAPABILITIES: ${NVIDIA_DRIVER_CAPABILITIES:-video,compute,utility}
|
|
OPENWEBUI_URL: ${OPENWEBUI_CONTAINER_URL:-http://open-webui:8080}
|
|
OPENWEBUI_API_KEY: ${OPENWEBUI_API_KEY}
|
|
OPENWEBUI_KB_NAME: ${OPENWEBUI_KB_NAME:-Homelab Library}
|
|
OPENWEBUI_KB_ID: ${OPENWEBUI_KB_ID:-}
|
|
PYTHONPATH: /app
|
|
JOB_TIMEOUT: ${JOB_TIMEOUT:-14400}
|
|
JOB_TTL: ${JOB_TTL:-86400}
|
|
RESULT_TTL: ${RESULT_TTL:-86400}
|
|
FAILURE_TTL: ${FAILURE_TTL:-86400}
|
|
volumes:
|
|
- ${TMP_HOST_DIR:-./tmp}:/tmpdl
|
|
- ${MODELS_HOST_DIR:-./models}:/root/.cache/huggingface
|
|
- ./app:/app
|
|
- /mnt/skynet-media/data/media/podx-video:/library/video
|
|
- /mnt/skynet-media/data/media/podx-audio:/library/audio
|
|
runtime: ${DOCKER_GPU_RUNTIME:-runc}
|
|
depends_on: [meili, redis, open-webui]
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "exit 0"]
|
|
extra_hosts:
|
|
- host.docker.internal:host-gateway
|
|
|
|
meili:
|
|
image: getmeili/meilisearch:v1.8
|
|
container_name: meili
|
|
env_file: [.env]
|
|
environment:
|
|
MEILI_NO_ANALYTICS: "true"
|
|
ports: ["7700:7700"]
|
|
volumes:
|
|
- ${MEILI_DATA_HOST_DIR:-./data/meili}:/meili_data
|
|
restart: unless-stopped
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
container_name: redis
|
|
volumes:
|
|
- ${REDIS_DATA_HOST_DIR:-./data/redis}:/data
|
|
restart: unless-stopped
|
|
|
|
ollama:
|
|
image: ollama/ollama:latest
|
|
container_name: ollama
|
|
ports:
|
|
- "11434:11434"
|
|
volumes:
|
|
- ${OLLAMA_DATA_HOST_DIR:-./data/ollama}:/root/.ollama
|
|
tty: true
|
|
restart: unless-stopped
|
|
|
|
open-webui:
|
|
image: ghcr.io/open-webui/open-webui:main
|
|
container_name: open-webui
|
|
environment:
|
|
- OLLAMA_BASE_URL=http://ollama:11434
|
|
- WEBUI_SECRET_KEY=${OPENWEBUI_SECRET_KEY:-}
|
|
volumes:
|
|
- ${OPENWEBUI_DATA_HOST_DIR:-./data/open-webui}:/app/backend/data
|
|
depends_on:
|
|
- ollama
|
|
ports:
|
|
- "3003:8080"
|
|
extra_hosts:
|
|
- host.docker.internal:host-gateway
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/api/health || exit 1"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
|
|
metube:
|
|
image: alexta69/metube:latest
|
|
container_name: metube
|
|
ports:
|
|
- "8081:8081"
|
|
environment:
|
|
- PUID=1000
|
|
- PGID=1000
|
|
- TZ=Europe/Prague
|
|
- DOWNLOAD_DIR=/downloads/video
|
|
- OUTPUT_TEMPLATE=%(uploader)s/%(upload_date)s - %(title)s.%(ext)s
|
|
# Optional: pass a cookies file to bypass consent/age walls
|
|
# - COOKIE_FILE=/config/cookies.txt
|
|
# Optional: yt-dlp options (JSON). Example enables Android client fallback
|
|
# - YTDL_OPTIONS={"extractor_args":{"youtube":{"player_client":"android"}}}
|
|
- YTDL_OPTIONS={"extractor_args":{"youtube":{"player_client":"android"}},"extract_flat":"in_playlist","concurrent_fragment_downloads":1,"writesubtitles":true,"writeautomaticsub":true,"subtitleslangs":["en.*"],"convertsubs":"srt","writeinfojson":true,"writethumbnail":true,"converttumbnails":"jpg"}
|
|
volumes:
|
|
- /mnt/skynet-media/data/media/podx-video:/downloads
|
|
# Optional cookies file on host → /config/cookies.txt inside container
|
|
# - /mnt/secure/cookies.txt:/config/cookies.txt:ro
|
|
restart: unless-stopped
|
|
|
|
# Scanner: watches /library and enqueues jobs (heavy jobs go to "transcribe" queue)
|
|
podx-scanner:
|
|
build:
|
|
context: ./app
|
|
args:
|
|
BASE_IMAGE: ${GPU_BASE_IMAGE:-python:3.11-slim}
|
|
container_name: podx-scanner
|
|
command: ["python", "scanner.py"]
|
|
env_file: [.env]
|
|
environment:
|
|
MEILI_URL: http://meili:7700
|
|
REDIS_URL: redis://redis:6379/0
|
|
LIBRARY_ROOT: /library
|
|
TRANSCRIPT_ROOT: /library
|
|
TRANSCRIBE_QUEUE: transcribe
|
|
SCAN_INTERVAL: 30
|
|
JOB_TIMEOUT: ${JOB_TIMEOUT:-14400}
|
|
JOB_TTL: ${JOB_TTL:-86400}
|
|
RESULT_TTL: ${RESULT_TTL:-86400}
|
|
FAILURE_TTL: ${FAILURE_TTL:-86400}
|
|
volumes:
|
|
- ./app:/app
|
|
- /mnt/skynet-media/data/media/podx-video:/library/video
|
|
- /mnt/skynet-media/data/media/podx-audio:/library/audio
|
|
depends_on: [redis]
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "exit 0"]
|
|
restart: unless-stopped
|
|
|
|
podx-rss:
|
|
build:
|
|
context: ./app
|
|
args:
|
|
BASE_IMAGE: ${GPU_BASE_IMAGE:-python:3.11-slim}
|
|
container_name: podx-rss
|
|
command: ["python", "rss_ingest.py"]
|
|
env_file: [.env]
|
|
environment:
|
|
MEILI_URL: http://meili:7700
|
|
REDIS_URL: redis://redis:6379/0
|
|
LIBRARY_ROOT: /library
|
|
TRANSCRIPT_ROOT: /library
|
|
PODCASTS_ROOT: /library
|
|
PODCASTS_PER_SHOW: ${PODCASTS_PER_SHOW:-true}
|
|
FEEDS_FILE: /library/feeds.txt
|
|
RSS_STATE_FILE: /library/.rss_state.json
|
|
RSS_SCAN_MINUTES: ${RSS_SCAN_MINUTES:-120}
|
|
RSS_CONNECT_TIMEOUT: ${RSS_CONNECT_TIMEOUT:-15}
|
|
RSS_READ_TIMEOUT: ${RSS_READ_TIMEOUT:-60}
|
|
AUDIO_MAX_MB: ${AUDIO_MAX_MB:-4096}
|
|
USER_AGENT: ${USER_AGENT:-podx-rss/1.0 (+local-archive)}
|
|
RSS_ONCE: ${RSS_ONCE:-0}
|
|
volumes:
|
|
- ./app:/app
|
|
- /mnt/skynet-media/data/media/podx-audio:/library
|
|
depends_on: [redis]
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "python - <<'PY'\nimport os,sys; p=os.getenv('FEEDS_FILE',''); sys.exit(0 if (p and os.path.exists(p)) else 1)\nPY"]
|
|
interval: 60s
|
|
timeout: 5s
|
|
retries: 3
|
|
restart: unless-stopped
|