Confinement 2 : You Smile You Lose

Confinement 2 : You Smile You Lose (IA Javascript)

Aujourd’hui, on va rigoler. La période est difficile, et les conséquences de ce virus ne sont qu’un détail dans l’océan de merde où je me noie. Au lieu de chialer, j’ai eu l’idée d’un petit projet pour oublier mes problèmes.



TLDR

J’ai fait une appli web qui va surveiller ton sourire via une intelligence artificielle en utilisant la webcam. Je te montre des vidéos marrantes, si tu souris tu perds ! C’est très drôle, ça fait du bien, c’est open source et ça n’utilise que des technologies web !

Prends-toi ça 5 minutes et rigole un coup.



Confinement 2 : You Smile You Lose


Si c’est fait, tu as dû rigoler à une ou deux vidéos au moins, c’est obligé.
Sinon soit t’es trop fort, soit t’as pas d’âme.

Tu veux rajouter une vidéo drôle ? Tu as vu un bug ? Il manque une feature ? Le projet est open-source et je t’invite à participer. J’ai l’approval de merge request très facile !

Si tu veux savoir pourquoi et comment j’ai construit cette appli, tu trouveras exactement ça dans la suite de l’article !



L’idée

Comme je te disais, la période est bien moisie. Du coup, comme toute personne un peu déprimée, je me balade de façon nonchalante sur YouTube. Je cherchais du contenu drôle pour me changer les idées.

Et c’est là que je suis tombé (une fois de plus) sur ces fameuses vidéos You Laugh You Lose. Le principe est simple, on pose des gens devant des vidéos drôles, s’ils rigolent c’est perdu.





Et là je me suis dit : « pourquoi pas faire la même chose mais grand public et dans le navigateur ? ».

J’ai tout ce qu’il me faut. Les vidéos viendraient de YouTube donc pas besoin de les héberger, de gérer le streaming ou de gérer un player. Ça serait un site statique pour simplifier l’hébergement de l’appli. Et surtout, je sais déjà comment détecter le sourire sur un visage.

Allez, je me donne 2 jours pour tout coder, héberger le projet, faire l’article que tu es entrain de lire en deux langues et passer le code en open-source sur mon GitHub. C’est parti !



Détection du sourire

Alors, crois-le ou non ça a été de loin la partie la plus simple et la plus rapide. Pour plusieurs raisons.

  • La première c’est qu’aujourd’hui la détection d’expression via des modèles d’intelligence artificielle sur le web c’est enfantin. N’importe qui peut en faire et/ou en mettre en place.
  • La seconde c’est que ce travail, je l’ai déjà fait dans un projet précédent !

Mais si rappelle-toi, quand j’ai fait ma connerie précédente avec les gifs.





Du coup, si tu veux absolument savoir comment cette partie marche en particulier, je t’invite à aller consulter l’article dédié.

En quelques mots, j’utilise la librairie face-api qui gère toute la partie complexe pour moi. Avec la webcam je load les modèles au lancement de l’appli. J’ai plus qu’à utiliser l’API haut niveau de face-api. Je check deux fois par seconde si l’utilisateur sourit ou non.



/**
 * Load models from faceapi
 * @async
 */
async function loadModels() {
    await faceapi.nets.tinyFaceDetector.loadFromUri("https://www.smile-lose.com/models")
    await faceapi.nets.faceExpressionNet.loadFromUri("https://www.smile-lose.com/models")
}

/**
 * Setup the webcam stream for the user.
 * On success, the stream of the webcam is set to the source of the HTML5 tag.
 * On error, the error is logged and the process continue.
 */
function setupWebcam() {
    navigator.mediaDevices
        .getUserMedia({ video: true, audio: false })
        .then(stream => {
            webcam.srcObject = stream
            if (isFirstRound) startFirstRound()
        })
        .catch(() => {
            document.getElementById("smileStatus").textContent = "camera not found"
            isUsingCamera = false
            if (isFirstRound) startFirstRound()
        })
}

/**
 * Determine if the user is smiling or not by getting the most likely current expression 
 * using the facepi detection object. Build a array to iterate on each possibility and 
 * pick the most likely.
 * @param {Object} expressions object of expressions
 * @return {Boolean}
 */
function isSmiling(expressions) {
    // filtering false positive
    const maxValue = Math.max(
        ...Object.values(expressions).filter(value => value <= 1)
    )

    const expressionsKeys = Object.keys(expressions)
    const mostLikely = expressionsKeys.filter(
        expression => expressions[expression] === maxValue
    )

    if (mostLikely[0] && mostLikely[0] == 'happy')
        return true

    return false
}

/**
 * Set an refresh interval where the faceapi will scan the face of the subject
 * and return an object of the most likely expressions.
 * Use this detection data to pick an expression and spread background gifs on divs.
 * @async
 */
async function refreshState() {
    setInterval(async() => {
        const detections = await faceapi
            .detectAllFaces(webcam, new faceapi.TinyFaceDetectorOptions())
            .withFaceExpressions()

        if (detections && detections[0] && detections[0].expressions) {
            isUsingCamera = true

            if (isSmiling(detections[0].expressions)) {
                currentSmileStatus = true
                document.getElementById("smileStatus").textContent = "YOU SMILE !"
            } else {
                document.getElementById("smileStatus").textContent = "not smiling"
            }
        }
    }, 400)
}


Tu trouveras dans le GitHub tout le code source du projet !



Gestion des vidéos

Comme dit précédemment, hors de question que je gère l’hébergement ou le streaming des vidéos. Je veux que le coût d’hébergement et d’utilisation de ce projet soient aux alentours de 0. Le fait que ça soit un site statique va beaucoup aider ici. Merci S3 + Cloudflare 🙂

Alors du coup, je me suis dit que j’allais utiliser le player de YouTube, les vidéos sur YouTube et l’API de YouTube. Merci YouTube. Le problème c’est que je veux rester sur mon site à moi. Donc je dois utiliser la version embed du player YouTube.

Pas de soucis, YouTube propose une API dédiée pour le lecteur embarqué !





J’ai jamais utilisé l’API de YouTube avant et je dois dire que c’était très simple à comprendre et à utiliser.



/**
 * Setup the youtube player using the official API
 */
function setupYoutubePlayer() {
    player = new YT.Player('player', {
        height: '100%',
        width: '100%',
        videoId: 'ewjkzE6X3BM',
        playerVars: {
            'controls': 0,
            'rel': 0,
            'showinfo': 0,
            'modestbranding': 1,
            'iv_load_policy': 3,
            'disablekb': 1
        },
        events: { 'onStateChange': onPlayerStateChange }
    })
}

/**
 * We want to show the intermissions when a video is over.
 * Listening to the event onPlayerStateChange of the youtube api.
 */
function onPlayerStateChange(event) {
    // 0 means the video is over
    if (event.data === 0) {
        player.stopVideo()
        showIntermission()
    }
}

/**
 * Entrypoint. This should be use once.
 */
function startFirstRound() {
    isFirstRound = false
    currentSmileStatus = false

    document.getElementById("loading").style.display = 'none'
    document.getElementById('intermission').className = 'fadeOut'

    player.playVideo()
}

/**
 * Showing the next video to the user.
 * This should be only trigger but the click on next video.
 */
function showNextVideo(event) {
    event.preventDefault()

    document.getElementById('loading').style.display = 'block'
    document.getElementById('result').style.display = 'none'

    if (listOfVideoIds.length) {
        const nextVideoId = extractRandomAvailableVideoId()
        player.loadVideoById({ videoId: nextVideoId })
        player.playVideo()

        setTimeout(() => {
            currentSmileStatus = false
            document.getElementById('intermission').className = 'fadeOut'
        }, 1000)
    } else {
        showCredit()
    }
}


Enfin, je gère les vidéos dans un simple tableau de string (id de vidéos YouTube) déclaré au tout début de l’application. À chaque fois que l’utilisateur clique pour voir une autre vidéo je pioche de façon random à l’intérieur. L’id est alors supprimée du tableau et insérée en source du lecteur YouTube embarqué. Easy !



TODO

J’ai fait ça de façon très rapide. Quand je l’ai teasé sur Twitter jeudi dernier j’avais encore rien commencé. Ouais je suis comme ça, je suis un fifou.





Du coup, il manque beaucoup de choses dans cette appli.
Tu veux participer ?

Il faudrait :

  • une gestion des scores
  • gestion d’autres lecteurs embarqués (dailymotion, vimeo, twitch)
  • un bouton skip pour tricher et passer à la vidéo suivante
  • une gestion de la détection du sourire moins stricte (plusieurs sourires avant de comptabiliser un vrai sourire)
  • détecter que l’utilisateur n’est plus dans le champ de la caméra (c’est facile)
  • cacher l’affichage de cartes YouTube à la fin de certaines vidéos
  • la première vidéo à l’initialisation pourrait être choisi de façon random aussi

Si quelque chose t’intéresse dans cette liste et que le Javascript te fait pas peur : tu trouveras le GitHub ici ! Encore une fois, j’ai l’approval de MR facile donc n’hésite pas.



Épilogue

Fin du défi. J’ai bien ri, ça m’a fait du bien. J’espère que ça va être pareil pour toi. C’est le maximum que je peux faire pour t’aider dans ce jour sans fin. En attendant, je te dis à lundi prochain !

Qui me parle ?

jesuisundev
Je suis un dev. En ce moment, je suis développeur backend senior / DevOps à Montréal pour un géant du jeux vidéo. Le dev est l'une de mes passions et j'écris comme je parle. Je continue à te parler quotidiennement sur mon Twitter. Tu peux m'insulter à cet e-mail ou le faire directement dans les commentaires juste en dessous. Y'a même une newsletter !

Pour me soutenir, la boutique officielle est disponible ! Sinon désactiver le bloqueur de pub et/ou utiliser les liens affiliés dans les articles, ça m'aide aussi.

7 commentaires sur “Confinement 2 : You Smile You Lose (IA Javascript)”

  1. Hello, super initiative ! 🙂
    Effectivement la doc des APIs de Google est généralement très bien faite ^^.
    Le très bon point est de proposer aux gens de contribuer, c’est pourquoi j’ai aussi des propositions de features :
    – Proposer de choisir entre différentes playlists de vidéos (playlist niveau Facile à Impossible)
    – Pouvoir faire ce défi à plusieurs en voyant la cam d’autres participants (sur invitation pour éviter les abus que l’on pourrait imaginer)
    – Ajouter un CONTRIBUTING.md
    – Ceux qui ont souri auront des filtres random qui s’appliquent sur leur cam, à voir lesquels

    Je n’hésiterais pas à faire des PRs aussi 😉

    Ça fait des années que je suis ton blog, continues comme ça en tout cas 👍

  2. Salut. Sympa l’idée et à la proposition de faire contribuer les gens. Ca donne envie de participer mais malheureusement, je débute (autodidacte et je commence à faire mumuse avec l’API de Discogs et Javascript). Je vais suivre et voir l’évolution de tout ça.
    Merci encore.

T'en penses quoi ?

Your email address will not be published. Required fields are marked *