2 : Répondre aux incidents et aux demandes d'assistance et d'évolution
- 1.2.1 Traiter des demandes concernant les services réseau et système, applicatifs
3: Développer la présence en ligne de l'organisation
- 1.3.1 Participer à l'évolution d'un site Web exploitant les données de l'organisation
5 : Mise à disposition des utilisateurs d'un service informatique
- 1.5.2 Déployer un service
Contexte
Code Rousseau possède une application permettant de transformer les énoncés et propositions des questions exportées de FileMaker en sons générés avec des solutions text-to-speech
L'objectif était de transformer cette application en API Symfony, en remplaçant l'ancien système de commandes PHP par des routes REST documentées et maintenables.
1. Interface et classe abstraite
L'entreprise utilisant trois API de synthèse vocale différentes, une interface commune a été mise en place pour définir les fonctions partagées entre les services :
<?php
namespace App\Service;
interface TtsServiceInterface
{
public function convertToSSML(
string $text,
array $dictionary,
string $lang
);
public function getVoice(string $voice, string $lang);
public function getOutputAudio(
string $ssml,
bool $isQuestion,
$configVoice
);
}
Une classe abstraite TtsService centralise les fonctionnalités communes :
setJSONFile: récupération du fichier JSONclearJSON: nettoyage et préparation des donnéestts: gestion de la logique principale (parcours du JSON, récupération des valeurs et appel des fonctions nécessaires)
2. Service de sélection de l'API
<?php
namespace App\Service;
use App\Service\ExempleAPIService;
class TtsSelectApi
{
public function selectApi($api): ExempleAPIService|string
{
switch ($api) {
case 'ExempleAPI':
return new ExempleAPIService();
break;
case '':
return "";
break;
case '':
return "";
break;
}
}
}
3. HttpClient ExempleAPI
L'appel à l'API Externe via un HttpClient Symfony.
La configuration est centralisée dans framework.yaml :
http_client:
scoped_clients:
ExempleAPI.client:
base_uri: '%env(ExempleAPI_BASE_URI)%'
headers:
xi-api-key: '%env(ExempleAPI_API_KEY)%'
Content-Type: 'application/json'
Le client est injecté via services.yaml :
public function __construct(
private HttpClientInterface $ExempleAPIClient
) {}
4. Traitement SSML
Le contenu du JSON est transformé en SSML avant d'être soumis à l'API.
Trois types de contenus sont traités : response, context et explanation.
Traitement de la réponse :
// Récupère la/les bonne réponse
$responses = $question['BonneReponse'];
$responses = $this->response(responses: $responses, and: $and);
public function response($responses, $and): string
{
$split = str_split(string: $responses);
$count = count(value: $split);
if ($count === 1) return " $split[0] ";
if ($count === 2) return " $split[0] $and $split[1] ";
if ($count === 3) return " $split[0] $split[1] $and $split[2] ";
// chaîne vide ou plus de 3 bonnes réponses
return " " . implode(separator: ' ', array: $split) . " ";
}
Traitement du contexte :
// Récupère le contexte
$context = $theText['Enonce'];
$proposals = $theText['Proposition'];
$contextLine = $this->context(
answer: $answer,
context: $context,
propositions: $proposals
);
public function context($answer, $context, $propositions): string{}
Traitement de l'explication :
// Récupère les explications
$explanations = $theText['TexteReponseDVD'];
$explanations = $this->explanation(
answer: $answer,
response: $responses,
explanations: $explanations
);
public function explanation($answer, $response, $explanations): string
{
$explanation = "<break time='1s' /><p>" . $explanations . "</p>";
$explanation .= "<s>$answer " . $response . "</s>";
return $explanation;
}
Adaptation du SSML selon l'API — méthode convertToSSML de ExempleAPIService :
$apiClass = (new TtsSelectApi())->selectApi(api: $api);
// Ajuste le SSML selon la classe enfant
$contextSSML = $apiClass->convertToSSML(
text: $contextLine,
dictionary: $dictionary,
lang: $lang
);
$explanationsSSML = $apiClass->convertToSSML(
text: $explanations,
dictionary: $dictionary,
lang: $lang
);
Modifie la prononciation selon la correction dans le pattern.json
$SSML = str_replace(
search: array_keys(array: $dictionary),
replace: array_values(array: $dictionary),
subject: $text
);
return $SSML;
}
5. Génération de l'audio
Méthode getVoice :
Permet de récupérer la configuration de la voix qui sera utilisée pour la génération de l'audio.
Méthode getOutputAudio :
Permet de générer un fichier audio en .mp3 en faisant appel à l'API externe, puis de le convertir en .wav en appelant la méthode mp3ToWav.
6. Conversion MP3 → WAV
Elle génère un fichier temporaire unique par exécution pour éviter les conflits si plusieurs utilisateurs lancent la commande simultanément, ajoute du silence avant et après l'audio, vérifie la présence de FFmpeg et détecte son chemin système avant exécution :
public function mp3ToWav(
string $mp3Data,
int $sampleRate = 48000,
string $prefix,
bool $isQuestion = false
): string {
$mp3Path = tempnam(
directory: sys_get_temp_dir(),
prefix: $prefix
) . '.mp3';
$wavPath = tempnam(
directory: sys_get_temp_dir(),
prefix: $prefix
) . '.wav';
file_put_contents(filename: $mp3Path, data: $mp3Data);
$ffmpeg = exec(command: "which ffmpeg");
if (!$ffmpeg) {
throw new \Exception(message: "ffmpeg n'est pas installé");
}
$silence = "adelay=1000";
if ($isQuestion) {
$silence .= ",apad-pad_dur=1";
}
$cmd = "({$ffmpeg}) -y -i " . escapeshellarg(arg: $mp3Path)
. " -ar {$sampleRate} -af "
. escapeshellarg(arg: $silence) . " "
. escapeshellarg(arg: $wavPath) . " 2>&1";
exec(command: $cmd, output: $output, result_code: $returnVar);
if (0 !== $returnVar) {
unlink(filename: $mp3Path);
if (file_exists(filename: $wavPath)) {
unlink(filename: $wavPath);
}
throw new \Exception(
message: "Erreur ffmpeg : "
. implode(separator: "\n", array: $output)
);
}
if (!file_exists(filename: $wavPath)) {
unlink(filename: $mp3Path);
throw new \Exception(
message: "Erreur : le fichier WAV n'a pas été créé !"
);
}
$wavData = file_get_contents(filename: $wavPath);
unlink(filename: $mp3Path);
unlink(filename: $wavPath);
return $wavData;
}
Fonctionnement global
Pour l'API : Le contrôleur reçoit une requête JSON, vérifie le Content-Type et le body. Pour la commande : Vérifie les paramètres de la commande. Puis sélectionne dynamiquement l'API via TtsSelectApi. Le service génère le SSML adapté à l'API et appelle l'API externe via le HttpClient. Pour l'API : Renvoie le fichier .wav et l'ajoute dans un dossier défini. Pour la commande : Ajoute les fichiers créés dans un dossier.