Ctrl K
ComunidadGuías…

smoketest.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Vérifie qu'une installation fonctionne réellement, chez l'utilisateur.

Distinct de tests.py, qui éprouve le code sur un service d'intégration. Celui-ci
répond à une autre question : « est-ce que ça marche sur *cette* machine ? ».
Il s'adresse à quelqu'un qui vient d'installer l'outil et veut le savoir avant
de compter dessus.

Il produit donc une vraie vidéo horodatée qu'on peut ouvrir et regarder, fait
apparaître une vraie notification, et dit ce qu'il en est de la session Blink et
du démarrage automatique. Il ne touche ni à vos clips, ni à vos vidéos : tout se
passe dans un dossier temporaire, sauf la vidéo de démonstration, laissée à
l'endroit indiqué pour que vous puissiez la regarder.

    blink2video smoketest
    blink2video smoketest --keep    conserve le dossier de travail
    blink2video smoketest --webrtc --report rapport.json
        teste H.264/ICE/DTLS/SRTP localement, sans compte ni caméra
"""

import argparse
import asyncio
import json
import shutil
import subprocess
import tempfile
from pathlib import Path

import runtime

LIBELLES = {
    "fr": {
        "aide_desc": "Vérifie qu'une installation fonctionne réellement, chez l'utilisateur.",
        "aide_keep": "conserver le dossier de travail au lieu de l'effacer",
        "aide_webrtc": "diagnostic WebRTC local seul, sans caméra ni notification",
        "aide_report": "écrire le résultat WebRTC dans ce fichier JSON",
        "erreur_report_sans_webrtc": "--report exige --webrtc",
        "marque_ok": "ok  ",
        "marque_echec": "ECHEC",
        "controle_installation_titre": "Contrôle de l'installation\n",
        "video_titre": "Vidéo",
        "ffmpeg_trouve": "ffmpeg trouvé",
        "ffmpeg_incrustation": "ffmpeg sait incruster du texte",
        "ffmpeg_incrustation_detail": "sans cela les vidéos sortiraient sans horodatage",
        "police_trouvee": "police trouvée",
        "clip_essai_fabrique": "clip d'essai fabriqué, entièrement noir",
        "video_horodatee_produite": "vidéo horodatée produite",
        "heure_dessinee": "l'heure est réellement dessinée dans l'image",
        "pixels_allumes_detail": "{allumes} pixels allumés dans la zone du cartouche",
        "a_regarder": "        à regarder : {chemin}",
        "dossier_travail_conserve": "        dossier de travail conservé : {chemin}",
        "notification_titre": "\nNotification",
        "toast_corps": "Contrôle d'installation : ceci est un essai.",
        "notification_envoyee": "notification envoyée",
        "notification_envoyee_detail":
            "elle doit apparaître à l'écran ; sinon, voir les limites du README",
        "compte_blink_titre": "\nCompte Blink",
        "session_enregistree": "session enregistrée",
        "session_absente_detail":
            "lancez « blink2video login » ; sans elle, ni téléchargement ni direct",
        "clips_deja_recuperes": "clips déjà récupérés",
        "clips_compte_detail": "{n} clip(s) dont {m} écarté(s)",
        "clips_aucun_detail": "aucun ; lancez « blink2video download »",
        "demarrage_auto_titre": "\nDémarrage automatique",
        "indetermine": "  indéterminé : {erreur}",
        "points_a_regarder": "{n} point(s) à regarder.",
        "installation_operationnelle": "Installation opérationnelle.",
    },
    "en": {
        "aide_desc": "Checks that an installation actually works, on the user's machine.",
        "aide_keep": "keep the working folder instead of deleting it",
        "aide_webrtc": "local WebRTC diagnostic only, no camera or notification",
        "aide_report": "write the WebRTC result to this JSON file",
        "erreur_report_sans_webrtc": "--report requires --webrtc",
        "marque_ok": "ok  ",
        "marque_echec": "FAIL ",
        "controle_installation_titre": "Installation check\n",
        "video_titre": "Video",
        "ffmpeg_trouve": "ffmpeg found",
        "ffmpeg_incrustation": "ffmpeg can burn in text",
        "ffmpeg_incrustation_detail": "without it, videos would come out with no timestamp",
        "police_trouvee": "font found",
        "clip_essai_fabrique": "test clip made, entirely black",
        "video_horodatee_produite": "timestamped video produced",
        "heure_dessinee": "the time is actually drawn in the image",
        "pixels_allumes_detail": "{allumes} lit pixels in the caption area",
        "a_regarder": "        to watch: {chemin}",
        "dossier_travail_conserve": "        working folder kept: {chemin}",
        "notification_titre": "\nNotification",
        "toast_corps": "Installation check: this is a test.",
        "notification_envoyee": "notification sent",
        "notification_envoyee_detail":
            "it should appear on screen; if not, see the README's limits",
        "compte_blink_titre": "\nBlink account",
        "session_enregistree": "session saved",
        "session_absente_detail":
            "run « blink2video login »; without it, neither download nor live view work",
        "clips_deja_recuperes": "clips already retrieved",
        "clips_compte_detail": "{n} clip(s), {m} excluded",
        "clips_aucun_detail": "none; run « blink2video download »",
        "demarrage_auto_titre": "\nAutostart",
        "indetermine": "  undetermined: {erreur}",
        "points_a_regarder": "{n} point(s) to look into.",
        "installation_operationnelle": "Installation working.",
    },
}


def _(cle: str, **valeurs) -> str:
    return runtime.traduire(LIBELLES, cle, **valeurs)


runtime.bootstrap()

import merge_daily as md


CONSTATS = []


def constat(ok: bool, intitule: str, detail: str = "") -> bool:
    marque = _("marque_ok") if ok else _("marque_echec")
    print(f"  {marque} {intitule}" + (f"\n        {detail}" if detail else ""))
    CONSTATS.append(ok)
    return ok


def pixels_allumes(ffmpeg: str, video: Path) -> int:
    """Compte les pixels clairs dans le bas de l'image, où s'écrit l'heure."""
    resultat = runtime.lancer(
        [ffmpeg, "-hide_banner", "-loglevel", "error", "-ss", "1",
         "-i", str(video), "-frames:v", "1",
         "-vf", "crop=iw:ih/6:0:ih*5/6,format=gray", "-f", "rawvideo", "-"],
        stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
        stderr=subprocess.DEVNULL, check=False,
    )
    return sum(1 for octet in resultat.stdout if octet > 60)


def main() -> int:
    parser = argparse.ArgumentParser(description=_("aide_desc"))
    parser.add_argument("--keep", action="store_true", help=_("aide_keep"))
    parser.add_argument("--timezone", default="Europe/Paris")
    parser.add_argument("--webrtc", action="store_true", help=_("aide_webrtc"))
    parser.add_argument("--report", type=Path, help=_("aide_report"))
    args = parser.parse_args()
    if args.report and not args.webrtc:
        parser.error(_("erreur_report_sans_webrtc"))
    if args.webrtc:
        return diagnostic_webrtc(args.report)

    print(_("controle_installation_titre"))

    print(_("video_titre"))
    try:
        ffmpeg = md.find_ffmpeg()
        constat(True, _("ffmpeg_trouve"), ffmpeg)
    except RuntimeError as erreur:
        constat(False, _("ffmpeg_trouve"), str(erreur))
        return bilan()

    constat(md.has_drawtext(ffmpeg), _("ffmpeg_incrustation"),
            "" if md.has_drawtext(ffmpeg)
            else _("ffmpeg_incrustation_detail"))

    try:
        police = md.find_font(None)
        constat(True, _("police_trouvee"), str(police))
    except RuntimeError as erreur:
        constat(False, _("police_trouvee"), str(erreur))
        return bilan()

    travail = Path(tempfile.mkdtemp(prefix="blink_smoketest_"))
    demonstration = runtime.dossier_sorties() / "smoketest.mp4"
    try:
        # Un clip noir : tout pixel allumé dans le bas de l'image ne pourra
        # venir que de l'horodatage, ce qui rend la preuve indiscutable.
        source = travail / "source.mp4"
        runtime.lancer(
            [ffmpeg, "-hide_banner", "-loglevel", "error", "-y", "-f", "lavfi",
             "-i", "color=c=black:s=1280x720:d=4:r=30", "-c:v", "libx264",
             "-preset", "ultrafast", "-pix_fmt", "yuv420p", str(source)],
            stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
            stderr=subprocess.PIPE, check=True,
        )
        constat(pixels_allumes(ffmpeg, source) == 0,
                _("clip_essai_fabrique"))

        import datetime as dt
        try:
            from zoneinfo import ZoneInfo
        except ImportError:  # Python 3.8 (build Windows 7, voir build-win7.yml) : pas de zoneinfo en stdlib.
            from backports.zoneinfo import ZoneInfo

        fuseau = ZoneInfo(args.timezone)
        maintenant = dt.datetime.now(dt.timezone.utc)
        clip = md.ClipInfo(created=maintenant, source=source, duration=4.0,
                           width=1280, height=720, fps=30.0, has_audio=False)
        ok, erreur = md.run_ffmpeg_batch(
            ffmpeg, [clip], 1280, 720, 30.0, fuseau,
            md.quote_filter_path(police), "veryfast", 23, demonstration,
        )
        constat(ok, _("video_horodatee_produite"), erreur)
        if ok:
            allumes = pixels_allumes(ffmpeg, demonstration)
            constat(allumes > 200, _("heure_dessinee"),
                    _("pixels_allumes_detail", allumes=allumes))
            print(_("a_regarder", chemin=demonstration))
    finally:
        if args.keep:
            print(_("dossier_travail_conserve", chemin=travail))
        else:
            shutil.rmtree(travail, ignore_errors=True)

    print(_("notification_titre"))
    try:
        import watch

        watch.toast("blink2video", _("toast_corps"),
                    url="http://127.0.0.1:8765/")
        constat(True, _("notification_envoyee"), _("notification_envoyee_detail"))
    except Exception as erreur:
        constat(False, _("notification_envoyee"), f"{type(erreur).__name__}: {erreur}")

    print(_("compte_blink_titre"))
    session = runtime.app_dir() / "blink_auth.json"
    if not session.is_file():
        constat(False, _("session_enregistree"), _("session_absente_detail"))
    else:
        constat(True, _("session_enregistree"), str(session))
        registre = md.load_json(md.DEFAULT_INPUT / md.DOWNLOAD_STATE, {})
        clips = registre.get("clips") or {}
        ecartes = sum(1 for c in clips.values() if isinstance(c, dict) and c.get("excluded"))
        constat(bool(clips), _("clips_deja_recuperes"),
                _("clips_compte_detail", n=len(clips), m=ecartes) if clips
                else _("clips_aucun_detail"))

    print(_("demarrage_auto_titre"))
    try:
        import autostart

        autostart.appliquer("status")
    except Exception as erreur:
        print(_("indetermine", erreur=erreur))

    return bilan()


def diagnostic_webrtc(rapport=None) -> int:
    """Utilisable dans un bundle sans console et sans Python installé."""
    from webrtc_probe import verifier_webrtc

    try:
        resultat = asyncio.run(verifier_webrtc(md.find_ffmpeg()))
        resultat.update(ok=True, version=runtime.version_affichee())
    except Exception as erreur:
        resultat = {"ok": False, "version": runtime.version_affichee(),
                    "error": f"{type(erreur).__name__}: {erreur}"}
    texte = json.dumps(resultat, ensure_ascii=False, indent=2)
    if rapport is not None:
        rapport.write_text(texte + "\n", encoding="utf-8")
    print(texte)
    return 0 if resultat["ok"] else 1


def bilan() -> int:
    echecs = CONSTATS.count(False)
    print()
    if echecs:
        print(_("points_a_regarder", n=echecs))
        return 1
    print(_("installation_operationnelle"))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())