Initial commit: MKV → AVI web app (Docker + Keycloak OIDC)
- App FastAPI : recherche dans un dossier média, download original ou conversion en AVI Xvid (tone-mapping HDR→SDR, audio FR auto, MP3 stéréo) - Auth Keycloak OIDC (Authlib) optionnelle - 3 composes : app seule, stack local (keycloak.local), production (NPM) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
# 🎬 MKV → AVI
|
||||
|
||||
Petite application web (Docker) pour parcourir un dossier média, **télécharger** un
|
||||
fichier d'origine ou le **convertir en AVI Xvid** lisible par les vieux lecteurs DivX/DVD.
|
||||
|
||||
La conversion reprend le pipeline qui a fait ses preuves :
|
||||
|
||||
- vidéo **Xvid** (`mpeg4 -vtag xvid`, qualité `qscale 4`) ;
|
||||
- audio **MP3 stéréo** (downmix 5.1 → 2.0) ;
|
||||
- sélection auto de la **piste française** en évitant l'audiodescription ;
|
||||
- **tone-mapping HDR → SDR** propre via `zscale` (les sources 4K HDR10/DV/HLG
|
||||
sont détectées automatiquement) ;
|
||||
- redimensionnement au choix (720 px « max compatibilité », 1280 px, ou original).
|
||||
|
||||
> ℹ️ Le décodage est **logiciel** : dans un conteneur, l'accélération matérielle
|
||||
> (VideoToolbox sur Mac, QSV/NVENC) n'est pas disponible. Comptez plusieurs minutes
|
||||
> à quelques dizaines de minutes par film selon le CPU.
|
||||
|
||||
## Démarrage rapide
|
||||
|
||||
```bash
|
||||
# Dossier contenant vos films (monté en lecture seule)
|
||||
export MEDIA_DIR=/chemin/vers/mes/films
|
||||
# Où écrire les AVI convertis (optionnel, défaut ./output)
|
||||
export OUTPUT_DIR=$PWD/output
|
||||
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Puis ouvrir **http://localhost:8080**.
|
||||
|
||||
- Rechercher un fichier dans le champ de recherche.
|
||||
- **⬇ Télécharger** : récupère le fichier d'origine.
|
||||
- **🎬 Convertir en AVI** : choisir définition + piste audio → suivre la progression →
|
||||
**⬇ Télécharger l'AVI** une fois terminé.
|
||||
|
||||
Les fichiers convertis restent aussi dans le dossier `OUTPUT_DIR`.
|
||||
|
||||
## Authentification Keycloak (OIDC)
|
||||
|
||||
L'app peut exiger une connexion Keycloak (flow **Authorization Code**, session par
|
||||
cookie). Elle est **désactivée tant que `OIDC_ISSUER` est vide** — pratique en dev local.
|
||||
Une fois configurée, **tout utilisateur authentifié** du realm a accès ; login/logout
|
||||
et nom de l'utilisateur sont gérés dans l'app.
|
||||
|
||||
### 1. Créer le client dans Keycloak
|
||||
- **Clients → Create client** : type `OpenID Connect`, Client ID `mkv2avi`.
|
||||
- **Client authentication : On** (client confidentiel) → un *secret* est généré
|
||||
(onglet **Credentials**).
|
||||
- **Valid redirect URIs** : `https://mkv2avi.mondomaine.fr/auth/callback`
|
||||
- **Valid post logout redirect URIs** : `https://mkv2avi.mondomaine.fr/`
|
||||
- **Web origins** : `+` (ou l'URL exacte).
|
||||
|
||||
### 2. Renseigner les variables
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# puis éditer .env :
|
||||
# OIDC_ISSUER=https://keycloak.mondomaine.fr/realms/mon-realm
|
||||
# OIDC_CLIENT_ID=mkv2avi
|
||||
# OIDC_CLIENT_SECRET=...(onglet Credentials)
|
||||
# SESSION_SECRET=$(openssl rand -hex 32)
|
||||
# SESSION_HTTPS_ONLY=true # si servi en HTTPS via Nginx Proxy Manager
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
> Derrière **Nginx Proxy Manager** (TLS) : uvicorn tourne avec `--proxy-headers`, donc
|
||||
> l'URL de callback est déduite en `https`. En cas de souci, forcez-la avec
|
||||
> `OIDC_REDIRECT_URI`. Activez `SESSION_HTTPS_ONLY=true` pour des cookies `Secure`.
|
||||
|
||||
### Stack tout-en-un (Keycloak inclus)
|
||||
|
||||
Pour tester/déployer **Keycloak + PostgreSQL + l'app** d'un coup, un compose dédié
|
||||
avec un realm pré-importé (client `mkv2avi` + utilisateur de test) est fourni :
|
||||
|
||||
```bash
|
||||
# 1. Rendre Keycloak résolvable sous le même nom côté navigateur ET côté conteneur
|
||||
echo "127.0.0.1 keycloak.local" | sudo tee -a /etc/hosts
|
||||
|
||||
# 2. (optionnel) dossier de films à convertir
|
||||
export MEDIA_DIR=/Volumes/data/films
|
||||
|
||||
# 3. Démarrer le stack
|
||||
docker compose -f docker-compose.keycloak.yml up -d --build
|
||||
```
|
||||
|
||||
| Service | URL | Identifiants |
|
||||
|---------|-----|--------------|
|
||||
| App MKV → AVI | http://localhost:8090 | `alex` / `alex` |
|
||||
| Console Keycloak | http://localhost:8080 | `admin` / `admin` |
|
||||
|
||||
Le realm `mkv2avi` (`keycloak/realm-mkv2avi.json`) contient déjà le client confidentiel
|
||||
(`secret : mkv2avi-secret-change-me`) et un utilisateur. **Change les secrets/mots de
|
||||
passe avant tout usage réel** (variables `OIDC_CLIENT_SECRET`, `SESSION_SECRET`,
|
||||
`KC_ADMIN_PASSWORD`, `KC_DB_PASSWORD`, et le `secret` dans le realm JSON).
|
||||
|
||||
> **Pourquoi `keycloak.local` ?** L'URL de Keycloak (l'*issuer*) doit être **identique**
|
||||
> depuis le navigateur et depuis le conteneur de l'app. L'alias réseau Docker
|
||||
> `keycloak.local` + la ligne `/etc/hosts` garantissent cette cohérence. En production
|
||||
> derrière Nginx Proxy Manager, on remplace tout ça par le vrai domaine
|
||||
> (`KC_HOSTNAME` + `OIDC_ISSUER` = `https://auth.mondomaine.fr/realms/mkv2avi`).
|
||||
|
||||
## Production derrière Nginx Proxy Manager
|
||||
|
||||
Fichier `docker-compose.prod.yml` : Keycloak en **mode production** (`start`), TLS
|
||||
terminé par NPM. Domaines : app = `https://mkv2avi.maisongrondin.fr`,
|
||||
Keycloak = `https://auth.mkv2avi.maisongrondin.fr`.
|
||||
|
||||
### 1. Secrets
|
||||
```bash
|
||||
cp .env.prod.example .env.prod
|
||||
# générer : openssl rand -hex 32 (pour OIDC_CLIENT_SECRET et SESSION_SECRET)
|
||||
# OIDC_CLIENT_SECRET doit être identique au "secret" du client dans
|
||||
# keycloak/realm-mkv2avi.json (ou régénéré ensuite dans la console Keycloak).
|
||||
```
|
||||
|
||||
### 2. Démarrage
|
||||
```bash
|
||||
docker compose --env-file .env.prod -f docker-compose.prod.yml up -d --build
|
||||
```
|
||||
Les services publient sur l'hôte : Keycloak `:8080`, app `:8090`.
|
||||
|
||||
### 3. Deux Proxy Hosts dans NPM
|
||||
| Domaine | Forward vers | Options |
|
||||
|---------|--------------|---------|
|
||||
| `auth.mkv2avi.maisongrondin.fr` | `http` `<IP_HOTE>` `8080` | SSL (Let's Encrypt) · Force SSL · Websockets |
|
||||
| `mkv2avi.maisongrondin.fr` | `http` `<IP_HOTE>` `8090` | SSL · Force SSL · Websockets |
|
||||
|
||||
Pour l'app, ajoute cette **Advanced / Custom Nginx Configuration** (téléchargements de
|
||||
gros fichiers sans coupure — même logique que ton fix Nextcloud) :
|
||||
```nginx
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
client_max_body_size 0;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
```
|
||||
|
||||
### 4. Points de vigilance
|
||||
- **DNS interne** : le conteneur `mkv2avi` doit résoudre `auth.mkv2avi.maisongrondin.fr`
|
||||
(il valide les tokens via cette URL). Si ton réseau ne fait pas de *hairpin NAT*,
|
||||
décommente le bloc `extra_hosts` du compose en pointant vers l'IP de NPM/hôte.
|
||||
- **Keycloak** sert en HTTP interne (`KC_HTTP_ENABLED=true`) et fait confiance aux
|
||||
en-têtes `X-Forwarded-*` de NPM (`KC_PROXY_HEADERS=xforwarded`) ; l'issuer reste
|
||||
figé sur l'URL publique (`KC_HOSTNAME`).
|
||||
- Change **tous** les mots de passe/secrets par défaut avant exposition.
|
||||
|
||||
## Déploiement sur TrueNAS SCALE
|
||||
|
||||
Utilisable comme *Custom App* (Docker) : montez le dataset des films sur `/media`
|
||||
(en lecture seule) et un dataset de sortie sur `/output`, exposez le port `8000`.
|
||||
|
||||
## Variables d'environnement
|
||||
|
||||
| Variable | Défaut | Rôle |
|
||||
|---------------|-----------|----------------------------------------------|
|
||||
| `MEDIA_DIR` | `/media` | Dossier parcouru (monté en lecture seule) |
|
||||
| `OUTPUT_DIR` | `/output` | Dossier des AVI convertis |
|
||||
| `MAX_WORKERS` | `1` | Conversions simultanées (1 = en série) |
|
||||
| `MAX_RESULTS` | `400` | Nb max de fichiers listés par recherche |
|
||||
| `OIDC_ISSUER` | *(vide)* | URL du realm Keycloak (vide = auth off) |
|
||||
| `OIDC_CLIENT_ID` | *(vide)* | Client ID Keycloak |
|
||||
| `OIDC_CLIENT_SECRET` | *(vide)* | Secret du client confidentiel |
|
||||
| `OIDC_REDIRECT_URI` | *(auto)* | Force l'URL de callback si besoin |
|
||||
| `SESSION_SECRET` | *(à changer)* | Clé de signature des cookies de session |
|
||||
| `SESSION_HTTPS_ONLY` | `false` | Cookies `Secure` (mettre `true` en HTTPS) |
|
||||
|
||||
## Développement local (sans Docker)
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
# ffmpeg avec le filtre zscale requis pour le HDR (sinon HDR non géré)
|
||||
MEDIA_DIR=/chemin/films OUTPUT_DIR=./output \
|
||||
uvicorn main:app --app-dir app --reload --port 8000
|
||||
```
|
||||
Reference in New Issue
Block a user