Phase 1 : moteur Python et commande rtg
- Paquet roadtripsgenerator (pyproject.toml), commande rtg : generer, etat, analyser, compresser, accueil, migrer - Réglages site.toml / voyage.toml (anciens mymaps.txt et moto.txt encore lus) ; qualité vidéo en trois crans - Sources laissées en place : traces découpées par journée d'après leurs horodatages (coupure à 4 h) ou la date du nom de fichier, photos copiées, HEIC converties en JPEG, position déduite de la trace pour les photos sans GPS - extraction_infos.sh et compress.sh portés en Python (plus de bash) ; sorties vidéo écrites sous un nom temporaire ; positions des rushs inchangés reprises du dernier export ; export vidéo conservé quand le disque des rushs est débranché - Pages, accueil et carte globale repris dans le paquet : sortie identique aux scripts (123 fichiers comparés sur 7 voyages) - Tests pytest sur un voyage d'exemple fabriqué à la volée Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
This commit is contained in:
1 parent
97b936fd3e
commit
f0e1c2dfce
23 files changed
+3083
-1
No files matched your search
@@ -0,0 +1,86 @@
|
||||
"""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
|
||||
Reference in new issue
Block a user