"""Petits outils communs : programmes externes, fichiers, affichage.""" import os import shutil import subprocess import sys EXT_PHOTOS = (".jpg", ".jpeg", ".heic", ".heif") EXT_VIDEOS = (".mp4", ".mov") def dire(message=""): print(message, flush=True) def programme(nom): """Chemin d'un programme externe (ffmpeg, ffprobe, exiftool), ou None.""" return shutil.which(nom) def exiger(nom, conseil): chemin = programme(nom) if not chemin: raise SystemExit(f"❌ {nom} introuvable. {conseil}") return chemin def est_cache(nom): """Fichiers cachés et « ._nom » AppleDouble que macOS dépose sur les disques non-HFS : jamais des médias.""" return nom.startswith(".") def lister_fichiers(sources, extensions, base="."): """Fichiers (chemins absolus, triés, sans doublon) trouvés dans une liste de fichiers et de dossiers ; chemins relatifs résolus depuis `base`. Les dossiers sont parcourus sur un niveau, comme les anciens scripts.""" vus, res = set(), [] for s in sources: p = os.path.abspath(os.path.join(base, os.path.expanduser(s))) if os.path.isdir(p): noms = sorted(os.listdir(p)) candidats = [os.path.join(p, n) for n in noms if not est_cache(n)] elif os.path.isfile(p): candidats = [p] else: continue for c in candidats: if os.path.isfile(c) and c.lower().endswith(extensions): cle = os.path.realpath(c) if cle not in vus: vus.add(cle) res.append(c) return res def lisible(video): """Vrai si ffprobe sait lire la vidéo (un encodage interrompu laisse un gros fichier sans index « moov »).""" if not os.path.exists(video) or os.path.getsize(video) == 0: return False r = subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", video], capture_output=True) return r.returncode == 0 and r.stdout.strip() != b"" def a_jour(sortie, source, force=False): return (not force and os.path.exists(sortie) and os.path.getsize(sortie) > 0 and os.path.getmtime(sortie) >= os.path.getmtime(source)) def coeurs_performance(): """Cœurs « performance » sur Apple Silicon (les cœurs d'efficience ralentiraient tout le pool de threads x265), sinon tous les cœurs.""" if sys.platform == "darwin": for cle in ("hw.perflevel0.logicalcpu", "hw.logicalcpu"): r = subprocess.run(["sysctl", "-n", cle], capture_output=True, text=True) if r.returncode == 0 and r.stdout.strip().isdigit(): return int(r.stdout.strip()) return os.cpu_count() or 4 def taille_lisible(octets): for unite in ("o", "Ko", "Mo", "Go"): if octets < 1024 or unite == "Go": return f"{octets:.0f} {unite}" if unite == "o" else f"{octets:.1f} {unite}" octets /= 1024