Files
RoadTripsGenerator/tests/test_chaine.py
T
FabriceandClaude Opus 5.5 f0e1c2dfce 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
2026-10-09 11:31:31 +00:00

124 lines
5.6 KiB
Python

"""Chaîne complète sur un petit voyage fabriqué à la volée.
Sources « mal rangées » exprès : une trace de deux jours au nom quelconque,
des photos dans un autre dossier (dont une HEIC et une sans GPS), une vidéo.
"""
import os
import shutil
import subprocess
from datetime import datetime, timedelta, timezone
import pytest
from rtg import config, site, sources
pytestmark = pytest.mark.skipif(not (shutil.which("exiftool") and shutil.which("ffmpeg")),
reason="exiftool et ffmpeg nécessaires")
UTC = timezone.utc
def _gpx(chemin):
"""Deux journées (Paris, UTC+2 en mai) ; un point à 00 h 30 locale doit
rester sur la veille (coupure à 4 h)."""
pts = []
t0 = datetime(2026, 5, 1, 6, 0, tzinfo=UTC) # 8 h locale
for i in range(60):
pts.append((48.80 + i * 0.002, 2.30 + i * 0.003, t0 + timedelta(minutes=2 * i)))
pts.append((48.95, 2.50, datetime(2026, 5, 1, 22, 30, tzinfo=UTC))) # 00 h 30 le 2 mai
t1 = datetime(2026, 5, 2, 7, 0, tzinfo=UTC)
for i in range(40):
pts.append((49.00 + i * 0.002, 2.60 + i * 0.002, t1 + timedelta(minutes=3 * i)))
seg = "\n".join(f'<trkpt lat="{la}" lon="{lo}"><time>{t.strftime("%Y-%m-%dT%H:%M:%SZ")}</time></trkpt>'
for la, lo, t in pts)
with open(chemin, "w") as f:
f.write(f'<?xml version="1.0"?><gpx version="1.1" creator="test" '
f'xmlns="http://www.topografix.com/GPX/1/1"><trk><name>test</name><trkseg>{seg}'
f'</trkseg></trk></gpx>')
def _photo(chemin, date_locale, gps=None):
from PIL import Image
im = Image.new("RGB", (3000, 2000), (90, 140, 200))
im.save(chemin, "JPEG")
args = ["exiftool", "-q", "-overwrite_original", f"-DateTimeOriginal={date_locale}"]
if gps:
args += [f"-GPSLatitude={gps[0]}", "-GPSLatitudeRef=N", f"-GPSLongitude={gps[1]}", "-GPSLongitudeRef=E"]
subprocess.run(args + [chemin], check=True)
def _heic(chemin, jpeg):
from PIL import Image
import pillow_heif
pillow_heif.register_heif_opener()
with Image.open(jpeg) as im:
im.save(chemin, format="HEIF", exif=im.info.get("exif"))
@pytest.fixture
def site_test(tmp_path):
racine = tmp_path / "site"
voyage = racine / "Balades" / "Test-05-2026"
sources_dir = tmp_path / "carte_sd"
for d in (voyage, sources_dir / "photos", sources_dir / "gopro"):
d.mkdir(parents=True)
_gpx(sources_dir / "export_68degres_quelconque.gpx")
_photo(str(sources_dir / "photos" / "IMG_0001.jpg"), "2026:05:01 09:30:00", gps=(48.85, 2.35))
# Sans GPS, prise à 9 h 00 locale le 1er mai = 7 h 00 UTC : 30e point de la trace
_photo(str(sources_dir / "photos" / "IMG_0002.jpg"), "2026:05:01 09:00:00")
_photo(str(tmp_path / "base.jpg"), "2026:05:02 10:00:00", gps=(49.01, 2.61))
_heic(str(sources_dir / "photos" / "IMG_0003.HEIC"), str(tmp_path / "base.jpg"))
subprocess.run(["ffmpeg", "-loglevel", "error", "-f", "lavfi", "-i", "testsrc=size=1280x720:rate=30",
"-f", "lavfi", "-i", "sine=frequency=440", "-t", "2", "-c:v", "libx264", "-c:a", "aac",
"-shortest", str(sources_dir / "gopro" / "GX010001.MP4")], check=True)
config.ecrire_toml(str(racine / "site.toml"), {"titre": "Site de test",
"video": {"preset": "ultrafast"}})
config.ecrire_toml(str(voyage / "voyage.toml"), {
"titre": "Test", "moto": "Moto d'essai",
"sources": {"traces": [str(sources_dir / "export_68degres_quelconque.gpx")],
"videos": [str(sources_dir / "gopro")],
"photos": [str(sources_dir / "photos")]}})
return racine, voyage
def test_decoupage_et_coupure(site_test):
racine, voyage = site_test
cfg = config.reglages_voyage(str(voyage), config.reglages_site(str(racine)))
a = sources.analyser(str(voyage), cfg)
assert sorted(j.isoformat() for j in a.journees) == ["2026-05-01", "2026-05-02"]
# Le point de 00 h 30 (heure de Paris) appartient au 1er mai
derniers = [p for t in a.journees[min(a.journees)] for s in t.segments for p in s.points]
assert derniers[-1].latitude == 48.95
def test_chaine_complete(site_test):
racine, voyage = site_test
assert site.generer(str(racine))
# Traces rangées par jour
assert sorted(os.listdir(voyage / "routes")) == ["2026-05-01.gpx", "2026-05-02.gpx"]
# Photos copiées, HEIC convertie en JPEG
assert sorted(os.listdir(voyage / "photos")) == ["IMG_0001.jpg", "IMG_0002.jpg", "IMG_0003.jpeg"]
csv = (voyage / "export_photos.csv").read_text()
assert "IMG_0001.jpg" in csv and "IMG_0003.jpeg" in csv
ligne2 = [l for l in csv.splitlines() if "IMG_0002" in l][0]
lat = float(ligne2.split(",")[2])
assert abs(lat - (48.80 + 30 * 0.002)) < 0.003 # position déduite de la trace
# Vidéo compressée en deux paliers lisibles
assert (voyage / "videos" / "GX010001.mp4").exists()
assert (voyage / "videos" / "GX010001_mobile.mp4").exists()
# Pages
assert sorted(os.listdir(voyage / "html")) == ["carte_2026-05-01.html", "carte_2026-05-02.html"]
assert (voyage / "index.html").exists() and (voyage / "photos_web" / "IMG_0001.jpg").exists()
accueil = (racine / "index.html").read_text()
assert "Site de test" in accueil and "Balades/Test-05-2026/index.html" in accueil
def test_deuxieme_passage_ne_refait_rien(site_test, capsys):
racine, voyage = site_test
site.generer(str(racine))
capsys.readouterr()
site.generer(str(racine))
sortie = capsys.readouterr().out
assert "Aucun nouvel encodage" in sortie
assert "0 réduite(s)" in sortie