> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.voicecheap.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Doubler le projet

> Doubler un projet déjà transcrit à partir de votre propre SRT traduit

# Doubler le projet

Générez les voix et mixez la sortie d'un projet existant à partir d'un SRT traduit que vous fournissez. VoiceCheap ne traduit rien sur ce point de terminaison : pas de moteur de traduction, pas de reformulation SmartSync, pas de glossaire. Chaque réplique est lue telle quelle sur le segment correspondant de la transcription du projet, puis mixée avec l'arrière-plan d'origine, les sous-titres et la synchronisation labiale facultative, exactement comme une traduction classique.

Utilisez-le lorsque la traduction est produite ailleurs (un traducteur humain, vos propres outils, un flux d'automatisation) et que vous n'avez besoin de VoiceCheap que pour générer les voix. L'exécution est asynchrone : interrogez [Obtenir le statut de la traduction](/docs/fr/api-reference/translation-status) ou écoutez les [Webhooks](/docs/fr/api-reference/webhooks).

## Prérequis

* Le projet doit exister et sa transcription doit être terminée : créez-le avec [Créer un projet](/docs/fr/api-reference/create-project) (transcription seule) ou [Démarrer la traduction](/docs/fr/api-reference/translate), puis attendez `transcriptionStatus: "success"`.
* Votre SRT doit couvrir la transcription du projet : chaque segment a besoin d'au moins une réplique, et chaque réplique doit tomber sur un segment. Exportez la transcription avec [Exporter la transcription du projet](/docs/fr/api-reference/project-transcript) et traduisez-la réplique par réplique pour conserver les codes temporels.
* La requête est facturée comme une traduction du même média, avec le même contrôle de crédits. Les modes de synchronisation labiale ajoutent leurs crédits supplémentaires habituels.

## Limite de concurrence

La création de projets, la traduction et le doublage vérifient tous la limite de traductions actives de votre compte (10 au maximum, parfois moins). Seules les traductions ou les opérations de doublage en cours occupent des places ; les projets limités à la transcription n’en occupent pas. Lorsque cette limite est atteinte, l’API renvoie `CONCURRENT_TRANSLATION_LIMIT_REACHED` (HTTP 429). La limite appliquée figure dans `details.limit`.

Une seconde exécution pour le même projet et la même langue cible est aussi refusée tant qu’une autre est en cours (`TRANSLATION_ALREADY_IN_PROGRESS`, HTTP 409).

## Requête

Ce point de terminaison accepte un corps JSON (`Content-Type: application/json`). Les options booléennes et numériques sont envoyées comme valeurs JSON, pas comme des chaînes.

### En-têtes

<ParamField header="x-api-key" type="string" required>
  Votre clé API VoiceCheap. Obtenez-en une sur [app.voicecheap.ai/page-api](https://app.voicecheap.ai/page-api).
</ParamField>

### Paramètres de chemin

<ParamField path="projectId" type="string" required>
  L'identifiant du projet renvoyé par [Créer un projet](/docs/fr/api-reference/create-project) ou [Démarrer la traduction](/docs/fr/api-reference/translate).
</ParamField>

### Paramètres du corps de la requête

<ParamField body="targetLanguage" type="string" required>
  La langue de `translatedSrt`, en minuscules, dans la même liste que [Démarrer la traduction](/docs/fr/api-reference/translate). Si le projet n'a pas encore de travail pour cette langue, un travail est créé et la langue est ajoutée au projet.
</ParamField>

<ParamField body="translatedSrt" type="string" required>
  Le contenu SRT final dans la langue cible (numéro de réplique, ligne de temps, texte). Maximum 1 000 000 caractères. Les répliques sont associées aux segments de transcription par les horodatages : une réplique qui commence à moins de 50 ms du début d'un segment lui est attribuée, sinon au segment qu'elle chevauche le plus. Plusieurs répliques sur un même segment sont jointes par une espace. Le minutage, le locuteur et l'ordre de chaque segment lu viennent toujours de la transcription du projet ; vos codes temporels servent uniquement à l'association.
</ParamField>

<ParamField body="webhookUrl" type="string">
  Un point de terminaison https qui reçoit désormais les [événements webhook](/docs/fr/api-reference/webhooks) de ce projet et remplace le point de terminaison configuré sur le compte.

  **Par défaut :** Le point de terminaison déjà enregistré sur le projet, puis le point de terminaison webhook du compte.
</ParamField>

<ParamField body="keepBackgroundMusic" type="boolean">
  Conserver l'audio d'arrière-plan d'origine (musique, ambiance) sous les voix générées.

  **Par défaut :** `true`
</ParamField>

<ParamField body="keepOriginalVoice" type="boolean">
  Conserver la voix d'origine discrètement sous la voix générée.

  **Par défaut :** `false`
</ParamField>

<ParamField body="originalVoiceVolume" type="number">
  Volume de la voix d'origine de `1` à `70` lorsque `keepOriginalVoice` est activé.

  **Par défaut :** `30`
</ParamField>

<ParamField body="voiceMode" type="string">
  Stratégie vocale : `cloning` (cloner les voix des intervenants du projet) ou `custom` (utiliser une voix du catalogue ou votre voix personnalisée enregistrée).

  **Par défaut :** `cloning`
</ParamField>

<ParamField body="voiceId" type="string">
  Requis lorsque `voiceMode` est `custom`. Utilisez un identifiant de `dubbing_voices_list` : une voix du catalogue ou une voix personnalisée
  enregistrée par le compte authentifié. La facturation en équipe ne donne pas accès aux voix enregistrées des autres membres.
</ParamField>

<ParamField body="voiceIsolatorOption" type="string">
  Mode d'isolation de la voix lorsque `keepBackgroundMusic` est activé : `studio` ou `realistic`.

  **Par défaut :** `studio`
</ParamField>

<ParamField body="subtitles" type="boolean">
  Incruster les sous-titres dans la vidéo de sortie. Nécessite un projet vidéo.

  **Par défaut :** `false`
</ParamField>

<ParamField body="subtitlesSource" type="string">
  Texte des sous-titres lorsque `subtitles` est activé : `translated` (votre SRT) ou `original` (la transcription source).

  **Par défaut :** `translated`
</ParamField>

<ParamField body="lipSyncMode" type="string">
  Lancer la synchronisation labiale une fois le doublage prêt : `standard`, `pro` ou `studio`. Les règles de forfait, de durée et de crédits sont les mêmes que sur [Démarrer la traduction](/docs/fr/api-reference/translate). Nécessite un projet vidéo.

  **Par défaut :** omettez le champ pour ignorer la synchronisation labiale.
</ParamField>

<ParamField body="activeSpeakerDetectionEnabled" type="boolean">
  Activer la détection du locuteur actif pour l'exécution de synchronisation labiale demandée.

  **Par défaut :** `false`
</ParamField>

<ParamField body="voiceCloningSettings" type="object">
  Réglage fin du clonage de voix, sous forme d'objet JSON avec `stability` et `similarity` entre `0` et `1`. Uniquement avec `voiceMode: "cloning"`. Voir [Démarrer la traduction](/docs/fr/api-reference/translate) pour le rôle de chaque valeur.
</ParamField>

<ParamField body="translationTimeSkips" type="array">
  Plages du temps source à laisser dans la langue d'origine, sous la forme `[{ "startTime": 10.2, "endTime": 12.8 }]`. Nécessite un projet vidéo et `keepBackgroundMusic: true`, ne peut pas être combiné avec `keepOriginalVoice` et ne peut pas chevaucher des segments de transcription.
</ParamField>

## Réponse

<ResponseField name="success" type="boolean" required>
  Toujours `true` pour les requêtes réussies.
</ResponseField>

<ResponseField name="message" type="string" required>
  Une confirmation lisible.
</ResponseField>

<ResponseField name="projectId" type="string" required>
  Le projet en cours de doublage.
</ResponseField>

<ResponseField name="workId" type="string" required>
  Le travail de traduction utilisé pour `targetLanguage`. Il correspond à `workId` dans les points de terminaison de statut, de détails et de transcription.
</ResponseField>

<ResponseField name="estimatedDuration" type="number" required>
  Estimation approximative du traitement en minutes : cinq minutes de traitement par minute entamée du média source.
</ResponseField>

```json Response theme={null}
{
  "success": true,
  "message": "Dubbing started successfully from the provided translation. Use the status endpoint to track progress.",
  "projectId": "project_123",
  "workId": "work_456",
  "estimatedDuration": 10
}
```

## Suivre l'exécution

Le projet passe au flux `translation` dès que le doublage démarre, donc [Obtenir le statut de la traduction](/docs/fr/api-reference/translation-status) suit la nouvelle exécution : `status` passe de `processing` à `success` (avec `translatedVideoUrl` et `translatedAudioUrl`) ou à `failed` (avec `error`). Les webhooks `project.translation.completed` et `project.translation.failed` sont envoyés avec le même `targetLanguage`. Dans [Obtenir les détails du projet](/docs/fr/api-reference/project-details), la version traduite indique `settings.dubbingMode: "dub-only"` et `settings.smartSync: false`.

Les échecs d’alignement apparaissent après l’acceptation de la requête POST. Le point de terminaison de statut renvoie `status: "failed"` avec `error.code` et `error.message` ; le webhook `project.translation.failed` porte le même code et le même message. `TRANSLATED_SRT_UNMATCHED_CUES` indique dans le message les numéros de répliques sans correspondance, et `TRANSLATED_SRT_MISSING_SEGMENTS` les index des segments sans réplique. Ces réponses asynchrones ne contiennent pas de champ `details` structuré. Corrigez le SRT et rappelez le point de terminaison : la version en échec reste dans l’historique et une nouvelle est créée.

### Vérifier le rythme

Exportez la transcription traduite en JSON avec [Exporter la transcription du projet](/docs/fr/api-reference/project-transcript). Chaque segment porte `finalSpeedFactor`, le rapport entre la voix générée et la fenêtre du segment. Raccourcissez les répliques qui dépassent environ `1.3`, puis relancez le doublage avec le SRT révisé.

## Exemple de flux

Une automatisation typique (par exemple un scénario n8n ou Make) enchaîne les points de terminaison comme suit :

1. `POST /v1/projects` avec le média, `targetLanguage` et une `originalLanguage` explicite (transcription seule).
2. Interrogez `GET /v1/translate/{projectId}/status` jusqu'à ce que `transcriptionStatus` vaille `success`, ou attendez le webhook `project.created`.
3. `POST /v1/projects/{projectId}/transcript` avec `{ "source": "original", "outputFormat": "srt" }` et traduisez les répliques en conservant la numérotation et les codes temporels.
4. `POST /v1/projects/{projectId}/dub` avec le SRT traduit.
5. Interrogez le point de terminaison de statut jusqu'à ce que `status` vaille `success`, ou attendez `project.translation.completed`.
6. Téléchargez `translatedVideoUrl` (ou `lipSync.videoUrl` si une synchronisation labiale a été demandée).
7. `DELETE /v1/translate/{projectId}` une fois les sorties stockées de votre côté.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.voicecheap.ai/v1/projects/project_123/dub" \
    -H "x-api-key: vc_your-api-key" \
    -H "Content-Type: application/json" \
    -d '{
      "targetLanguage": "english",
      "translatedSrt": "1\n00:00:00,000 --> 00:00:02,400\nWelcome to this lesson.\n\n2\n00:00:02,400 --> 00:00:05,100\nToday we talk about fractions.",
      "keepBackgroundMusic": true,
      "subtitles": true,
      "subtitlesSource": "translated"
    }'
  ```

  ```typescript TypeScript theme={null}
  interface DubProjectResponse {
    success: boolean;
    message: string;
    projectId: string;
    workId: string;
    estimatedDuration: number;
  }

  const API_BASE_URL = 'https://api.voicecheap.ai/v1';
  const headers = { 'x-api-key': 'vc_your-api-key', 'Content-Type': 'application/json' };

  async function waitForStatus(projectId: string, isReady: (status: any) => boolean): Promise<any> {
    while (true) {
      const status = await fetch(`${API_BASE_URL}/translate/${projectId}/status`, { headers }).then((response) => response.json());
      if (status.status === 'failed') throw new Error(`${status.error?.code}: ${status.error?.message}`);
      if (isReady(status)) return status;
      await new Promise((resolve) => setTimeout(resolve, 15_000));
    }
  }

  async function dubExistingProject(projectId: string, translatedSrt: string): Promise<string> {
    // 1. The project was created earlier with POST /v1/projects; wait for its transcript.
    await waitForStatus(projectId, (status) => status.transcriptionStatus === 'success');

    // 2. Send the translation you produced from the exported transcript.
    const dubResponse = await fetch(`${API_BASE_URL}/projects/${projectId}/dub`, {
      method: 'POST',
      headers,
      body: JSON.stringify({ targetLanguage: 'english', translatedSrt, keepBackgroundMusic: true }),
    });
    if (!dubResponse.ok) {
      const error = await dubResponse.json();
      throw new Error(`${error.code}: ${error.message}`);
    }
    const { workId } = (await dubResponse.json()) as DubProjectResponse;
    console.log('Dubbing started on work', workId);

    // 3. Wait for the dubbed output.
    const completed = await waitForStatus(projectId, (status) => status.status === 'success');
    return completed.translatedVideoUrl as string;
  }
  ```

  ```python Python theme={null}
  import time
  import requests

  API_BASE_URL = 'https://api.voicecheap.ai/v1'
  HEADERS = {'x-api-key': 'vc_your-api-key'}


  def wait_for_status(project_id, is_ready):
      while True:
          status = requests.get(f'{API_BASE_URL}/translate/{project_id}/status', headers=HEADERS).json()
          if status['status'] == 'failed':
              raise RuntimeError(f"{status['error']['code']}: {status['error']['message']}")
          if is_ready(status):
              return status
          time.sleep(15)


  def dub_existing_project(project_id, translated_srt):
      wait_for_status(project_id, lambda status: status['transcriptionStatus'] == 'success')

      response = requests.post(
          f'{API_BASE_URL}/projects/{project_id}/dub',
          headers=HEADERS,
          json={'targetLanguage': 'english', 'translatedSrt': translated_srt, 'keepBackgroundMusic': True},
      )
      response.raise_for_status()
      print('Dubbing started on work', response.json()['workId'])

      completed = wait_for_status(project_id, lambda status: status['status'] == 'success')
      return completed['translatedVideoUrl']
  ```
</CodeGroup>

## Erreurs

| Statut | Code | Description |
| - | - | - |
| 400 | `VALIDATION_ERROR` | Un champ du corps est absent ou invalide (`details` liste chaque champ) |
| 400 | `INVALID_TRANSLATED_SRT` | translatedSrt ne contient pas de répliques SRT valides |
| 400 | `INVALID_BOOLEAN_VALUE` | Une option booléenne n'a pas été envoyée comme booléen JSON |
| 400 | `INVALID_NUMBER_VALUE` | Une option numérique n'a pas été envoyée comme nombre JSON |
| 400 | `DURATION_DETECTION_FAILED` | La durée média enregistrée du projet n'est pas disponible |
| 400 | `VOICE_ID_REQUIRED` | Le mode voix personnalisées a été sélectionné sans ID de voix |
| 400 | `VOICE_ID_NOT_ALLOWED_WITH_CLONING` | Un ID de voix a été combiné avec le clonage vocal |
| 400 | `VOICE_CLONING_SETTINGS_NOT_ALLOWED` | Les paramètres de clonage ont été combinés avec le mode voix personnalisées |
| 400 | `TRANSLATION_TIME_SKIP_CONFLICT` | Les sauts temporels sont en conflit avec une autre option audio |
| 400 | `TRANSLATION_TIME_SKIP_REQUIRES_BACKGROUND` | Les sauts temporels nécessitent un audio d’arrière-plan |
| 400 | `TRANSLATION_TIME_SKIP_OUT_OF_RANGE` | Un saut temporel dépasse la durée du média |
| 400 | `SUBTITLES_NOT_AVAILABLE_FOR_AUDIO` | Des sous-titres incrustés ont été demandés pour une entrée audio |
| 400 | `LIPSYNC_NOT_AVAILABLE_FOR_AUDIO` | Une synchronisation labiale a été demandée pour une entrée audio |
| 400 | `TRANSLATION_TIME_SKIP_NOT_AVAILABLE_FOR_AUDIO` | Des sauts temporels ont été demandés pour une entrée audio |
| 400 | `LIPSYNC_VIDEO_TOO_LONG` | Une synchronisation labiale a été demandée pour un média plus long que la durée prise en charge |
| 401 | `MISSING_API_KEY` | La clé API est requise |
| 401 | `INVALID_API_KEY_FORMAT` | La clé API doit commencer par `vc_` |
| 401 | `INVALID_API_KEY` | La clé API fournie est invalide |
| 403 | `SUBSCRIPTION_REQUIRED` | L'accès à l'API nécessite un forfait Starter ou supérieur |
| 403 | `FORBIDDEN` | Vous n'avez pas l'autorisation d'accéder au projet |
| 403 | `INSUFFICIENT_CREDITS` | Crédits insuffisants pour doubler ce projet |
| 403 | `CUSTOM_VOICE_ACCESS_DENIED` | La voix sélectionnée ne figure pas dans le catalogue et n’est pas une voix personnalisée enregistrée par le compte authentifié. |
| 403 | `LIPSYNC_MODE_ACCESS_DENIED` | Le mode de synchronisation labiale premium n'est pas disponible sur le forfait actuel |
| 404 | `PROJECT_NOT_FOUND` | Le projet n'existe pas |
| 409 | `DUB_ONLY_REQUIRES_TRANSCRIPTION` | La transcription du projet est encore en cours de traitement ou a échoué |
| 409 | `TRANSLATION_ALREADY_IN_PROGRESS` | Un doublage est déjà en cours pour cette langue sur le projet |
| 429 | `RATE_LIMIT_EXCEEDED` | Trop de requêtes (limite : 10 requêtes par minute) |
| 429 | `CONCURRENT_TRANSLATION_LIMIT_REACHED` | Limite de traductions simultanées du compte atteinte ; voir `details.limit` |
| 500 | `INTERNAL_ERROR` | Erreur serveur inattendue |

Les échecs d'alignement détectés pendant l'exécution (`TRANSLATED_SRT_UNMATCHED_CUES`, `TRANSLATED_SRT_MISSING_SEGMENTS`) sont renvoyés par le point de terminaison de statut et le webhook d'échec, pas par cette requête.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.