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
+147
@@ -0,0 +1,147 @@
|
||||
"""Réglages du site (site.toml) et des voyages (voyage.toml).
|
||||
|
||||
Un réglage absent d'un voyage prend la valeur du site, puis la valeur par
|
||||
défaut ci-dessous. Les anciens fichiers (mymaps.txt, moto.txt) sont encore lus
|
||||
quand les nouveaux n'existent pas ; `rtg migrer` écrit les nouveaux.
|
||||
"""
|
||||
import copy
|
||||
import os
|
||||
import re
|
||||
|
||||
try:
|
||||
import tomllib
|
||||
except ModuleNotFoundError: # Python 3.10
|
||||
import tomli as tomllib
|
||||
|
||||
SITE_TOML = "site.toml"
|
||||
VOYAGE_TOML = "voyage.toml"
|
||||
|
||||
DEFAUTS = {
|
||||
"titre": "Nos Road-Trips",
|
||||
"adresse_publique": "https://roadtrip.ouaibe.fr/",
|
||||
"accueil_nb_recents": 2,
|
||||
"publication": {"mode": "sur_place", "destination": "", "adresse_videos": ""},
|
||||
"video": {
|
||||
"qualite": "equilibree", # econome | equilibree | haute
|
||||
"encodeur": "processeur", # processeur | materiel
|
||||
"preset": "medium",
|
||||
"fps_max": 60,
|
||||
"jobs": 0, # 0 = automatique
|
||||
"threads": 0,
|
||||
},
|
||||
"photos": {"grand_cote": 1600, "qualite": 80, "positions_deduites": True},
|
||||
"journees": {"fuseau": "Europe/Paris", "coupure_heure": 4},
|
||||
"carte_globale": {"mymaps": []},
|
||||
# Propres à un voyage
|
||||
"rubrique": "",
|
||||
"moto": "",
|
||||
"sources": {"traces": ["routes"], "videos": ["rushs"], "photos": ["photos"]},
|
||||
"exclusions": {"fichiers": []},
|
||||
}
|
||||
|
||||
# Qualité « humaine » → paramètres d'encodeur. « equilibree » = réglages
|
||||
# historiques de compress.sh (CRF 26 HEVC / 25 H.264).
|
||||
QUALITES = {
|
||||
"econome": {"crf_hevc": 28, "crf_h264": 27, "vtq_hd": 55, "vtq_sd": 53},
|
||||
"equilibree": {"crf_hevc": 26, "crf_h264": 25, "vtq_hd": 60, "vtq_sd": 58},
|
||||
"haute": {"crf_hevc": 23, "crf_h264": 22, "vtq_hd": 68, "vtq_sd": 65},
|
||||
}
|
||||
|
||||
|
||||
def fusion(base, ajout):
|
||||
"""Fusion récursive : les valeurs de `ajout` l'emportent."""
|
||||
r = copy.deepcopy(base)
|
||||
for k, v in (ajout or {}).items():
|
||||
if isinstance(v, dict) and isinstance(r.get(k), dict):
|
||||
r[k] = fusion(r[k], v)
|
||||
else:
|
||||
r[k] = copy.deepcopy(v)
|
||||
return r
|
||||
|
||||
|
||||
def lire_toml(chemin):
|
||||
if not os.path.exists(chemin):
|
||||
return {}
|
||||
with open(chemin, "rb") as f:
|
||||
return tomllib.load(f)
|
||||
|
||||
|
||||
def trouver_racine(depart="."):
|
||||
"""Premier dossier, en remontant depuis `depart`, qui contient site.toml ;
|
||||
à défaut `depart` lui-même."""
|
||||
d = os.path.abspath(depart)
|
||||
while True:
|
||||
if os.path.exists(os.path.join(d, SITE_TOML)):
|
||||
return d
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
return os.path.abspath(depart)
|
||||
d = parent
|
||||
|
||||
|
||||
def _mymaps_ancien(racine):
|
||||
f = os.path.join(racine, "mymaps.txt")
|
||||
if not os.path.exists(f):
|
||||
return []
|
||||
with open(f, encoding="utf-8") as fh:
|
||||
return [l.strip() for l in fh if l.strip() and not l.strip().startswith("#")]
|
||||
|
||||
|
||||
def reglages_site(racine):
|
||||
brut = lire_toml(os.path.join(racine, SITE_TOML))
|
||||
cfg = fusion(DEFAUTS, brut)
|
||||
if not brut.get("carte_globale", {}).get("mymaps"):
|
||||
cfg["carte_globale"]["mymaps"] = _mymaps_ancien(racine)
|
||||
return cfg
|
||||
|
||||
|
||||
def reglages_voyage(dossier, site=None):
|
||||
"""Réglages effectifs d'un voyage : défauts ← site ← voyage.toml (← moto.txt)."""
|
||||
brut = lire_toml(os.path.join(dossier, VOYAGE_TOML))
|
||||
cfg = fusion(site if site is not None else DEFAUTS, brut)
|
||||
if not brut.get("moto"):
|
||||
f = os.path.join(dossier, "moto.txt")
|
||||
if os.path.exists(f):
|
||||
with open(f, encoding="utf-8") as fh:
|
||||
cfg["moto"] = fh.readline().strip()
|
||||
if not cfg.get("titre") or "titre" not in brut:
|
||||
cfg["titre"] = os.path.basename(dossier.rstrip("/")).replace("-", " ").replace("_", " ")
|
||||
return cfg
|
||||
|
||||
|
||||
def parametres_video(cfg):
|
||||
"""Paramètres d'encodage effectifs (dictionnaire plat)."""
|
||||
v = cfg["video"]
|
||||
q = QUALITES.get(v.get("qualite", "equilibree"), QUALITES["equilibree"])
|
||||
return dict(q, encodeur="vt" if v.get("encodeur") == "materiel" else "cpu",
|
||||
preset=v.get("preset", "medium"), fps_max=int(v.get("fps_max", 60)),
|
||||
jobs=int(v.get("jobs", 0)), threads=int(v.get("threads", 0)))
|
||||
|
||||
|
||||
# --- Écriture (rtg migrer, interface) -----------------------------------------
|
||||
def _toml_valeur(v):
|
||||
if isinstance(v, bool):
|
||||
return "true" if v else "false"
|
||||
if isinstance(v, (int, float)):
|
||||
return str(v)
|
||||
if isinstance(v, list):
|
||||
return "[" + ", ".join(_toml_valeur(x) for x in v) + "]"
|
||||
s = str(v).replace("\\", "\\\\").replace('"', '\\"')
|
||||
return f'"{s}"'
|
||||
|
||||
|
||||
def ecrire_toml(chemin, donnees, entete=""):
|
||||
"""Écrit un TOML simple (valeurs, listes, une profondeur de tables)."""
|
||||
lignes = [f"# {l}" for l in entete.splitlines()] + ([""] if entete else [])
|
||||
for k, v in donnees.items():
|
||||
if not isinstance(v, dict):
|
||||
lignes.append(f"{k} = {_toml_valeur(v)}")
|
||||
for k, v in donnees.items():
|
||||
if isinstance(v, dict):
|
||||
lignes += ["", f"[{k}]"] + [f"{kk} = {_toml_valeur(vv)}" for kk, vv in v.items()]
|
||||
with open(chemin, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(lignes) + "\n")
|
||||
|
||||
|
||||
def nom_lisible(nom):
|
||||
return re.sub(r"[-_]+", " ", nom).strip()
|
||||
Reference in new issue
Block a user