#!/bin/bash # ============================================================================= # photobooth-config.sh - Gestion rapide de la config photobooth-app # ============================================================================= # Usage: # bash photobooth-config.sh quality high|low|status # bash photobooth-config.sh livestream on|off|status # bash photobooth-config.sh frontpage manuel|direct|noprint|status # bash photobooth-config.sh restart # ============================================================================= set -uo pipefail API_URL="http://127.0.0.1:8083" API_USER="admin" API_PASS="PhotoBooth2026!" GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' CYAN='\033[0;36m' NC='\033[0m' # --- Auth --- get_token() { curl -s -X POST "${API_URL}/api/admin/auth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=password&username=${API_USER}&password=${API_PASS}" \ | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])' } get_config() { local token="$1" curl -s "${API_URL}/api/admin/config/app" \ -H "Authorization: Bearer ${token}" } set_config() { local token="$1" local config="$2" curl -s -X PATCH "${API_URL}/api/admin/config/app" \ -H "Authorization: Bearer ${token}" \ -H "Content-Type: application/json" \ -d "${config}" > /dev/null } # ============================================================================= # QUALITE # ============================================================================= quality_status() { local token token=$(get_token) local config config=$(get_config "$token") local capture_w quality capture_w=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);print(c["backends"]["group_backends"][0]["backend_config"]["CAPTURE_CAM_RESOLUTION_WIDTH"])') quality=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);print(c["backends"]["group_backends"][0]["backend_config"]["original_still_quality"])') if [ "$capture_w" -ge 4000 ]; then echo -e "Qualite: ${GREEN}HAUTE${NC} (${capture_w}px, quality ${quality})" else echo -e "Qualite: ${YELLOW}BASSE${NC} (${capture_w}px, quality ${quality})" fi } quality_high() { local token token=$(get_token) set_config "$token" '{ "backends": { "group_backends": [{ "enabled": true, "description": "pi camera module V3", "backend_config": { "orientation": "1: 0°", "backend_type": "Picamera2", "camera_num": 0, "CAPTURE_CAM_RESOLUTION_WIDTH": 4608, "CAPTURE_CAM_RESOLUTION_HEIGHT": 3072, "PREVIEW_CAM_RESOLUTION_WIDTH": 2304, "PREVIEW_CAM_RESOLUTION_HEIGHT": 1536, "LIVEVIEW_RESOLUTION_WIDTH": 750, "LIVEVIEW_RESOLUTION_HEIGHT": 500, "framerate_still_mode": 20, "framerate_video_mode": 25, "frame_skip_count": 4, "optimized_lowlight_short_exposure": false, "videostream_quality": "MEDIUM", "original_still_quality": 100 } }] }, "mediaprocessing": { "full_still_length": 4608, "preview_still_length": 2304, "thumbnail_still_length": 960 } }' echo -e "${GREEN}Qualite HAUTE activee${NC} (4608x3072, quality 100)" echo " -> systemctl --user restart photobooth-app" } quality_low() { local token token=$(get_token) set_config "$token" '{ "backends": { "group_backends": [{ "enabled": true, "description": "pi camera module V3", "backend_config": { "orientation": "1: 0°", "backend_type": "Picamera2", "camera_num": 0, "CAPTURE_CAM_RESOLUTION_WIDTH": 2304, "CAPTURE_CAM_RESOLUTION_HEIGHT": 1536, "PREVIEW_CAM_RESOLUTION_WIDTH": 1152, "PREVIEW_CAM_RESOLUTION_HEIGHT": 768, "LIVEVIEW_RESOLUTION_WIDTH": 750, "LIVEVIEW_RESOLUTION_HEIGHT": 500, "framerate_still_mode": 20, "framerate_video_mode": 25, "frame_skip_count": 4, "optimized_lowlight_short_exposure": false, "videostream_quality": "MEDIUM", "original_still_quality": 90 } }] }, "mediaprocessing": { "full_still_length": 2304, "preview_still_length": 1200, "thumbnail_still_length": 480 } }' echo -e "${YELLOW}Qualite BASSE activee${NC} (2304x1536, quality 90)" echo " -> systemctl --user restart photobooth-app" } # ============================================================================= # LIVESTREAM # ============================================================================= livestream_status() { local token token=$(get_token) local config config=$(get_config "$token") local idle_stream idle_stream=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);print(c["uisettings"]["enable_livestream_when_idle"])') if [ "$idle_stream" = "True" ]; then echo -e "Livestream idle: ${GREEN}ACTIVE${NC}" else echo -e "Livestream idle: ${RED}DESACTIVE${NC}" fi } livestream_on() { local token token=$(get_token) set_config "$token" '{ "uisettings": { "enable_livestream_when_idle": true } }' echo -e "${GREEN}Livestream idle ACTIVE${NC}" echo " Les visiteurs voient la camera en direct sur l'ecran d'accueil" } livestream_off() { local token token=$(get_token) set_config "$token" '{ "uisettings": { "enable_livestream_when_idle": false } }' echo -e "${RED}Livestream idle DESACTIVE${NC}" echo " L'ecran d'accueil affiche le slideshow" } # ============================================================================= # FRONTPAGE TEXT # ============================================================================= frontpage_status() { local token token=$(get_token) local config config=$(get_config "$token") local text text=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);t=c["uisettings"]["FRONTPAGE_TEXT"];print("impression" if "Impression" in t else "noprint" if "Appui long" not in t else "unknown")') echo -e "Frontpage: ${CYAN}${text}${NC}" } # --- HTML templates --- FRONTPAGE_MANUEL='
Bienvenue !
Appui court = Photo
Appui long (3s) = Demande d'\''impression
Photomaton propos\u00e9 par Les Sapins Du Web. En l'\''utilisant, vous acceptez que les photos puissent etre utilis\u00e9es par Les Sapins Du Web et ses partenaires.
Les Sapins Du Web
' FRONTPAGE_DIRECT='
Bienvenue !
Appui court = Photo
Appui long (3s) = Impression
Photomaton propos\u00e9 par Les Sapins Du Web. En l'\''utilisant, vous acceptez que les photos puissent etre utilis\u00e9es par Les Sapins Du Web et ses partenaires.
Les Sapins Du Web
' FRONTPAGE_NOPRINT='
Bienvenue !
Appuyez sur le bouton pour prendre une photo
Photomaton propos\u00e9 par Les Sapins Du Web. En l'\''utilisant, vous acceptez que les photos puissent etre utilis\u00e9es par Les Sapins Du Web et ses partenaires.
Les Sapins Du Web
' frontpage_set() { local mode="$1" local token token=$(get_token) local html="" case "$mode" in manuel) html="$FRONTPAGE_MANUEL" ;; direct) html="$FRONTPAGE_DIRECT" ;; noprint) html="$FRONTPAGE_NOPRINT" ;; esac # Utiliser python3 pour construire le JSON proprement (gestion des quotes) python3 -c " import json, urllib.request data = json.dumps({'uisettings': {'FRONTPAGE_TEXT': '''${html}'''}}).encode() req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH') req.add_header('Authorization', 'Bearer ${token}') req.add_header('Content-Type', 'application/json') urllib.request.urlopen(req) " case "$mode" in manuel) echo -e "${CYAN}Frontpage: MODE MANUEL${NC}" echo " Appui court = Photo / Appui long = Demande d'impression" ;; direct) echo -e "${CYAN}Frontpage: MODE DIRECT${NC}" echo " Appui court = Photo / Appui long = Impression" ;; noprint) echo -e "${CYAN}Frontpage: SANS IMPRESSION${NC}" echo " Appuyez sur le bouton pour prendre une photo" ;; esac } # ============================================================================= # ACTIONS IMAGE # ============================================================================= ACTIONS_DIR="${HOME}/photobooth-data/actions-backups" actions_init() { mkdir -p "$ACTIONS_DIR" } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DISPLAY_ACTIONS_PY="${SCRIPT_DIR}/display_actions.py" actions_list() { local token token=$(get_token) local config config=$(get_config "$token") echo "=== Actions image actuelles ===" echo "$config" | python3 "$DISPLAY_ACTIONS_PY" } actions_backup() { local name="$1" actions_init local token token=$(get_token) local config config=$(get_config "$token") local filepath="${ACTIONS_DIR}/${name}.json" echo "$config" | python3 -c " import sys, json c = json.load(sys.stdin) with open('${filepath}', 'w') as f: json.dump(c['actions'], f, indent=2, ensure_ascii=False) " local count count=$(python3 -c "import json;print(len(json.load(open('${filepath}'))['image']))") echo -e "${GREEN}Backup sauvegarde: ${filepath}${NC}" echo " ${count} action(s) image" } actions_restore() { local name="$1" actions_init local filepath="${ACTIONS_DIR}/${name}.json" if [ ! -f "$filepath" ]; then echo -e "${RED}Backup introuvable: ${filepath}${NC}" echo "Backups disponibles:" actions_list_backups return 1 fi local token token=$(get_token) python3 -c " import json, urllib.request with open('${filepath}') as f: actions = json.load(f) data = json.dumps({'actions': actions}).encode() req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH') req.add_header('Authorization', 'Bearer ${token}') req.add_header('Content-Type', 'application/json') urllib.request.urlopen(req) " local count count=$(python3 -c "import json;print(len(json.load(open('${filepath}'))['image']))") echo -e "${GREEN}Backup restaure: ${name}${NC}" echo " ${count} action(s) image chargees" echo " -> systemctl --user restart photobooth-app" } actions_list_backups() { actions_init echo "=== Backups disponibles ===" local found=false for f in "${ACTIONS_DIR}"/*.json; do if [ -f "$f" ]; then found=true local basename basename=$(basename "$f" .json) local count count=$(python3 -c "import json;print(len(json.load(open('${f}'))['image']))" 2>/dev/null || echo "?") local date date=$(stat -c '%y' "$f" 2>/dev/null | cut -d. -f1) echo " ${basename} (${count} actions, ${date})" fi done if ! $found; then echo " (aucun)" fi } actions_show() { local name="$1" actions_init local filepath="${ACTIONS_DIR}/${name}.json" if [ ! -f "$filepath" ]; then echo -e "${RED}Backup introuvable: ${filepath}${NC}" return 1 fi echo "=== Backup: ${name} ===" python3 "$DISPLAY_ACTIONS_PY" --file "$filepath" } actions_move() { local from="$1" local to="$2" local token token=$(get_token) local tmpfile tmpfile=$(mktemp /tmp/photobooth-config-XXXXXX.json) get_config "$token" > "$tmpfile" python3 -c " import json, urllib.request, sys with open('${tmpfile}') as f: config = json.load(f) actions = config['actions']['image'] count = len(actions) f, t = int('${from}'), int('${to}') if f < 0 or f >= count or t < 0 or t >= count: print(f'Erreur: index invalide (0-{count-1})') sys.exit(1) item = actions.pop(f) actions.insert(t, item) config['actions']['image'] = actions data = json.dumps({'actions': config['actions']}).encode() req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH') req.add_header('Authorization', 'Bearer ${token}') req.add_header('Content-Type', 'application/json') urllib.request.urlopen(req) print(f'Action [{f}] deplacee vers [{t}]') for i, a in enumerate(actions): marker = ' <--' if i == t else '' print(f' [{i}] {a[\"name\"]}{marker}') " rm -f "$tmpfile" } actions_enable() { local index="$1" local token token=$(get_token) local tmpfile tmpfile=$(mktemp /tmp/photobooth-config-XXXXXX.json) get_config "$token" > "$tmpfile" python3 -c " import json, urllib.request, sys with open('${tmpfile}') as f: config = json.load(f) actions = config['actions']['image'] idx = int('${index}') if idx < 0 or idx >= len(actions): print(f'Erreur: index invalide (0-{len(actions)-1})') sys.exit(1) actions[idx]['trigger']['ui_trigger']['show_button'] = True data = json.dumps({'actions': config['actions']}).encode() req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH') req.add_header('Authorization', 'Bearer ${token}') req.add_header('Content-Type', 'application/json') urllib.request.urlopen(req) print(f'Action [{idx}] {actions[idx][\"name\"]} : bouton VISIBLE') " rm -f "$tmpfile" } actions_disable() { local index="$1" local token token=$(get_token) local tmpfile tmpfile=$(mktemp /tmp/photobooth-config-XXXXXX.json) get_config "$token" > "$tmpfile" python3 -c " import json, urllib.request, sys with open('${tmpfile}') as f: config = json.load(f) actions = config['actions']['image'] idx = int('${index}') if idx < 0 or idx >= len(actions): print(f'Erreur: index invalide (0-{len(actions)-1})') sys.exit(1) actions[idx]['trigger']['ui_trigger']['show_button'] = False data = json.dumps({'actions': config['actions']}).encode() req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH') req.add_header('Authorization', 'Bearer ${token}') req.add_header('Content-Type', 'application/json') urllib.request.urlopen(req) print(f'Action [{idx}] {actions[idx][\"name\"]} : bouton CACHE') " rm -f "$tmpfile" } actions_duplicate() { local index="$1" local token token=$(get_token) local tmpfile tmpfile=$(mktemp /tmp/photobooth-config-XXXXXX.json) get_config "$token" > "$tmpfile" python3 -c " import json, urllib.request, copy, sys with open('${tmpfile}') as f: config = json.load(f) actions = config['actions']['image'] idx = int('${index}') if idx < 0 or idx >= len(actions): print(f'Erreur: index invalide (0-{len(actions)-1})') sys.exit(1) new_action = copy.deepcopy(actions[idx]) new_action['name'] = new_action['name'] + ' (copie)' new_action['trigger']['ui_trigger']['show_button'] = False actions.insert(idx + 1, new_action) data = json.dumps({'actions': config['actions']}).encode() req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH') req.add_header('Authorization', 'Bearer ${token}') req.add_header('Content-Type', 'application/json') urllib.request.urlopen(req) print(f'Action [{idx}] dupliquee vers [{idx+1}]') for i, a in enumerate(actions): marker = ' <-- copie' if i == idx + 1 else '' print(f' [{i}] {a[\"name\"]}{marker}') " rm -f "$tmpfile" } actions_remove() { local indices="$@" local token token=$(get_token) local tmpfile tmpfile=$(mktemp /tmp/photobooth-config-XXXXXX.json) get_config "$token" > "$tmpfile" python3 -c " import json, urllib.request, sys with open('${tmpfile}') as f: config = json.load(f) actions = config['actions']['image'] indices = sorted([int(x) for x in '${indices}'.split()], reverse=True) for idx in indices: if idx < 0 or idx >= len(actions): print(f'Erreur: index {idx} invalide (0-{len(actions)-1})') sys.exit(1) removed = [] for idx in indices: removed.append(f'[{idx}] {actions[idx][\"name\"]}') actions.pop(idx) data = json.dumps({'actions': config['actions']}).encode() req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH') req.add_header('Authorization', 'Bearer ${token}') req.add_header('Content-Type', 'application/json') urllib.request.urlopen(req) print('Actions supprimees:') for r in removed: print(f' - {r}') print() print('Actions restantes:') for i, a in enumerate(actions): print(f' [{i}] {a[\"name\"]}') print(f' Total: {len(actions)} action(s)') " rm -f "$tmpfile" } # ============================================================================= # RESTART # ============================================================================= do_restart() { echo "Redemarrage de photobooth-app..." systemctl --user restart photobooth-app sleep 3 if systemctl --user is-active photobooth-app &>/dev/null; then echo -e "${GREEN}photobooth-app redemarre avec succes${NC}" else echo -e "${RED}ERREUR: photobooth-app n'a pas redemarre${NC}" systemctl --user status photobooth-app fi } # ============================================================================= # AIDE # ============================================================================= show_help() { cat << 'HELP' =============================================================================== photobooth-config.sh - Gestion rapide de la config photobooth-app =============================================================================== USAGE: photobooth-config.sh [arguments] COMMANDES GENERALES: status, s Afficher la config actuelle (qualite, livestream, frontpage) restart, r Redemarrer photobooth-app help, -h, --help Afficher cette aide QUALITE D'IMAGE: quality status Voir la qualite actuelle quality high Haute qualite (4608x3072, JPEG 100%) Meilleure qualite d'impression mais consomme plus de RAM quality low Basse qualite (2304x1536, JPEG 90%) Economise la RAM, suffisant pour impression Selphy Note: necessite un restart apres changement LIVESTREAM: livestream status Voir si le livestream idle est actif livestream on Activer le livestream sur l'ecran d'accueil Les visiteurs voient la camera en direct livestream off Desactiver le livestream idle L'ecran d'accueil affiche le slideshow TEXTE D'ACCUEIL (FRONTPAGE): frontpage status Voir le mode actuel frontpage manuel "Appui long (3s) = Demande d'impression" Pour les events avec validation admin frontpage direct "Appui long (3s) = Impression" Pour les events avec impression automatique frontpage noprint "Appuyez sur le bouton pour prendre une photo" Pas de mention d'impression ACTIONS IMAGE: actions list Lister toutes les actions avec leurs parametres L'index [0] = simple clic, [1] = double clic, etc. actions backup Sauvegarder les actions actuelles Ex: actions backup hopnbloc actions restore Restaurer un backup d'actions Ex: actions restore hopnbloc Note: necessite un restart apres actions backups Lister les backups disponibles actions show Voir le contenu d'un backup actions move Deplacer une action dans la liste Ex: actions move 3 0 (met l'action 3 en premier) Important: l'ordre determine le bouton physique [0] = simple clic [1] = double clic [2] = triple clic [3] = quadruple clic actions enable Rendre le bouton visible sur l'ecran tactile actions disable Cacher le bouton de l'ecran tactile actions dup Dupliquer une action (copie inseree apres) actions remove [i...] Supprimer une ou plusieurs actions Ex: actions remove 5 (supprime l'action 5) Ex: actions remove 3 5 7 (supprime les actions 3, 5 et 7) EXEMPLES: # Preparer un event Hop'N Bloc photobooth-config.sh actions backup config-avant photobooth-config.sh actions restore hopnbloc photobooth-config.sh frontpage manuel photobooth-config.sh livestream on photobooth-config.sh quality low photobooth-config.sh restart # Passer en haute qualite pour un event special photobooth-config.sh quality high photobooth-config.sh restart # Apres l'event, tout remettre photobooth-config.sh actions restore config-avant photobooth-config.sh restart =============================================================================== HELP } # ============================================================================= # MAIN # ============================================================================= CMD="${1:-help}" ARG="${2:-status}" case "$CMD" in quality|q) case "$ARG" in high|haute|hq) quality_high ;; low|basse|lq) quality_low ;; status|info) quality_status ;; *) echo "Usage: $0 quality {high|low|status}" ;; esac ;; livestream|ls) case "$ARG" in on|true|1) livestream_on ;; off|false|0) livestream_off ;; status|info) livestream_status ;; *) echo "Usage: $0 livestream {on|off|status}" ;; esac ;; frontpage|fp) case "$ARG" in manuel|manual) frontpage_set manuel ;; direct) frontpage_set direct ;; noprint|sans|off) frontpage_set noprint ;; status|info) frontpage_status ;; *) echo "Usage: $0 frontpage {manuel|direct|noprint|status}" ;; esac ;; actions|a) case "$ARG" in list|ls) actions_list ;; backup|save) if [ -z "${3:-}" ]; then echo "Usage: $0 actions backup " echo " Exemple: $0 actions backup hopnbloc" exit 1 fi actions_backup "$3" ;; restore|load) if [ -z "${3:-}" ]; then echo "Usage: $0 actions restore " actions_list_backups exit 1 fi actions_restore "$3" ;; backups) actions_list_backups ;; show) if [ -z "${3:-}" ]; then echo "Usage: $0 actions show " actions_list_backups exit 1 fi actions_show "$3" ;; move|mv) if [ -z "${3:-}" ] || [ -z "${4:-}" ]; then echo "Usage: $0 actions move " echo " Exemple: $0 actions move 3 0 (deplace l'action 3 en position 0)" exit 1 fi actions_move "$3" "$4" ;; enable|on) if [ -z "${3:-}" ]; then echo "Usage: $0 actions enable " actions_list exit 1 fi actions_enable "$3" ;; disable|off) if [ -z "${3:-}" ]; then echo "Usage: $0 actions disable " actions_list exit 1 fi actions_disable "$3" ;; dup|duplicate|copy) if [ -z "${3:-}" ]; then echo "Usage: $0 actions dup " actions_list exit 1 fi actions_duplicate "$3" ;; remove|rm|del|delete) if [ -z "${3:-}" ]; then echo "Usage: $0 actions remove [index...]" echo " Exemple: $0 actions remove 5" echo " Exemple: $0 actions remove 3 5 7" actions_list exit 1 fi shift 2 actions_remove "$@" ;; *) echo "Usage: $0 actions {list|backup|restore|backups|show|move|enable|disable|dup|remove}" ;; esac ;; restart|r) do_restart ;; status|s) echo "=== Photobooth Config ===" quality_status livestream_status frontpage_status ;; help|-h|--help) show_help ;; *) show_help ;; esac