elevenlabs_field-1.0.0-beta7/modules/elevenlabs_bytescale/src/Bytescale.php

modules/elevenlabs_bytescale/src/Bytescale.php
<?php

namespace Drupal\elevenlabs_bytescale;

use Drupal\Component\Uuid\Php;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Utility\Token;
use Drupal\elevenlabs_bytescale\Form\BytescaleForm;
use Drupal\field\Entity\FieldConfig;
use Drupal\file\FileStorageInterface;
use GuzzleHttp\Client;

/**
 * Bytescale API creator.
 */
class Bytescale {

  /**
   * Array with all instructions.
   */
  protected array $instructions = [];

  /**
   * Target field config.
   */
  protected array $targetFieldSettings = [];

  /**
   * The entity.
   */
  protected ContentEntityInterface $entity;

  /**
   * The http client.
   */
  protected Client $client;

  /**
   * The file storage interface.
   */
  protected FileStorageInterface $fileStorage;

  /**
   * The file system.
   */
  protected FileSystemInterface $fileSystem;

  /**
   * The token system.
   */
  protected Token $token;

  /**
   * Account ID.
   */
  private string $accountId;

  /**
   * API Key.
   */
  private string $apiKey;

  /**
   * The base path.
   */
  private string $basePath = 'https://api.bytescale.com/v2/';

  /**
   * CDN endpoint.
   */
  private string $cdnEndpoint = 'https://upcdn.io/';

  /**
   * The UUID system.
   */
  protected Php $uuid;

  /**
   * Constructs a new Bytescale object.
   *
   * @param \GuzzleHttp\Client $client
   *   Http client.
   * @param \Drupal\Core\Config\ConfigFactory $configFactory
   *   The config factory.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityType
   *   The entity type manager interface.
   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
   *   The file system interface.
   * @param \Drupal\Component\Uuid\Php $uuid
   *   The uuid system.
   * @param \Drupal\Core\Utility\Token $token
   *   The token system.
   */
  public function __construct(Client $client, ConfigFactory $configFactory, EntityTypeManagerInterface $entityType, FileSystemInterface $fileSystem, Php $uuid, Token $token) {
    $this->client = $client;
    $config = $configFactory->get(BytescaleForm::CONFIG_NAME);
    $this->apiKey = $config->get('api_key') ?? '';
    $this->accountId = $config->get('account_id') ?? '';
    $this->fileStorage = $entityType->getStorage('file');
    $this->fileSystem = $fileSystem;
    $this->uuid = $uuid;
    $this->token = $token;
  }

  /**
   * Create an audio file from a field value.
   *
   * @param mixed $fieldValues
   *   The elevenlabs field values.
   * @param string $originFieldName
   *   The field name to save from.
   * @param string $targetFieldName
   *   The field name to save to.
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity to save to.
   *
   * @return \Drupal\file\Entity\File
   *   The file entity.
   */
  public function concatenateAudios($fieldValues, $originFieldName, $targetFieldName, ContentEntityInterface $entity) {
    if (empty($fieldValues[0])) {
      return NULL;
    }

    $this->entity = $entity;
    $this->setInstructions($fieldValues);
    $this->ensureTargetFieldSettings($targetFieldName);
    $this->uploadFiles();
    $this->mergeFiles();
    return $this->generateFileEntity();
  }

  /**
   * Create instructions.
   *
   * @param mixed $fieldValues
   *   The elevenlabs field values.
   */
  private function setInstructions($fieldValues) {
    foreach ($fieldValues as $value) {
      if (is_array($value) && !empty($value['target_id'])) {
        /** @var \Drupal\file\Entity\File */
        $file = $this->fileStorage->load($value['target_id']);
        $path = $this->fileSystem->realpath($file->getFileUri());
        $this->instructions[$value['target_id']] = [
          'filePath' => $path,
        ];
      }
    }
    $this->instructions = array_values($this->instructions);
  }

  /**
   * Ensures the target field config.
   *
   * @param string $fieldName
   *   The field name to save to.
   */
  private function ensureTargetFieldSettings($fieldName) {
    $config = FieldConfig::loadByName($this->entity->getEntityTypeId(), $this->entity->bundle(), $fieldName);
    $this->targetFieldSettings = $config->getSettings();
  }

  /**
   * Upload the files.
   */
  private function uploadFiles() {
    $i = 0;
    foreach ($this->instructions as $id => $instruction) {
      // Generate a master first.
      if (!$i) {
        $this->deleteFileById('master');
        $this->uploadFile('master', $instruction['filePath']);
      }
      $this->uploadFile($id, $instruction['filePath']);
      $i++;
    }
  }

  /**
   * Merge the files.
   */
  private function mergeFiles() {
    $i = 0;
    $masterFile = $this->getCdnUrl('master', 'save/audio');
    $query[] = 'f=mp3';
    $options['headers']['content-type'] = 'application/json';
    // Generate all the files.
    foreach ($this->instructions as $id => $instruction) {
      if ($i) {
        $query[] = 'append=' . $this->getFilePath($id);
      }
      $i++;
    }
    $payload = json_encode([
      'destination' => [
        'fileName' => "final.mp3",
        'folderPath' => $this->getFolderPath(),
      ],
    ]);
    $response = json_decode($this->makeRequest($masterFile, $query, 'POST', $payload, $options, FALSE), TRUE);
    $this->waitForAudioJob($response['jobId']);
  }

  /**
   * Generates the file entity.
   *
   * @return \Drupal\file\Entity\File
   *   The file entity.
   */
  protected function generateFileEntity() {
    $fileName = 'final.mp3';
    $tmpFile = $this->generateUniqueFile();
    file_put_contents($tmpFile, $this->downloadFile('final'));
    $dir = $this->token->replace($this->targetFieldSettings['uri_scheme'] . '://' . rtrim($this->targetFieldSettings['file_directory'], '/'));
    $this->fileSystem->prepareDirectory($dir, FileSystemInterface::CREATE_DIRECTORY);
    $destination = $dir . '/' . $fileName;
    $destination = $this->fileSystem->copy($tmpFile, $destination, FileSystemInterface::EXISTS_RENAME);
    /** @var \Drupal\file\Entity\File */
    $file = $this->fileStorage->create([
      'uri' => $destination,
    ]);
    $file->setPermanent();
    $file->save();
    // Cleanup.
    unlink($tmpFile);
    return $file;
  }

  /**
   * Wait for job.
   *
   * @param string $jobId
   *   The job id.
   */
  private function waitForAudioJob($jobId) {
    while (TRUE) {
      $response = $this->getAudioJob($jobId);
      if (!in_array($response['status'], [
        'Pending',
        'Cancelling',
        'Running',
      ])) {
        break;
      }
      sleep(1);
    }
  }

  /**
   * Upload one file.
   *
   * @param int $id
   *   The fid of the file.
   * @param string $path
   *   The path of the file.
   */
  private function uploadFile($id, $path) {
    $options['headers']['content-type'] = 'audio/mpeg';
    $query[] = 'filename=' . $id . '.mp3';
    $query[] = 'folderPath=' . $this->getFolderPath();
    $query[] = 'filePath=' . $this->getFolderPath() . '/' . $id . '.mp3';
    $headers = get_headers($this->getCdnUrl($id), 1);
    // Skip if it is already uploaded.
    if (strpos($headers[0], '200 OK') === FALSE) {
      $this->makeRequest('accounts/' . $this->accountId . '/uploads/binary', $query, 'POST', file_get_contents($path), $options);
    }
  }

  /**
   * Download one file.
   *
   * @param string $id
   *   The fid of the file.
   */
  private function downloadFile($id) {
    $file = $this->getCdnUrl($id);
    return file_get_contents($file);
  }

  /**
   * Get status of a job.
   *
   * @param string $jobId
   *   The job id.
   *
   * @return array
   *   The job response.
   */
  private function getAudioJob($jobId) {
    $response = $this->makeRequest('accounts/' . $this->accountId . '/jobs/ProcessAudioJob/' . $jobId);
    return json_decode($response, TRUE);
  }

  /**
   * Delete one file by id.
   *
   * @param int $id
   *   The fid of the file.
   */
  private function deleteFileById($id) {
    $path = $this->getFolderPath() . '/' . $id . '.mp3';
    $this->deleteFile($path);
  }

  /**
   * Delete one file.
   *
   * @param string $path
   *   The path of the file.
   */
  private function deleteFile($path) {
    $query[] = 'filePath=' . $path;
    $this->makeRequest('accounts/' . $this->accountId . '/files', $query, 'DELETE');
  }

  /**
   * Get cdn url of file.
   *
   * @param int $id
   *   The fid of the file.
   * @param string $type
   *   The type of file to get.
   */
  private function getCdnUrl($id, $type = "raw") {
    return $this->cdnEndpoint . $this->accountId . "/$type/" . $this->entity->getEntityTypeId() . '-' . $this->entity->id() . '/' . $id . '.mp3';
  }

  /**
   * Get path of file.
   *
   * @param int $id
   *   The fid of the file.
   */
  private function getFilePath($id) {
    return $this->getFolderPath() . '/' . $id . '.mp3';
  }

  /**
   * Get folder path.
   */
  private function getFolderPath() {
    return '/' . $this->entity->getEntityTypeId() . '-' . $this->entity->id();
  }

  /**
   * Generate tmp file.
   *
   * @param string $ext
   *   The extension.
   *
   * @return string
   *   The file path.
   */
  private function generateUniqueFile($ext = 'mp3') {
    $tmpFile = $this->fileSystem->getTempDirectory() . '/' . $this->uuid->generate() . '.' . $ext;
    $this->tmpFiles[] = $tmpFile;
    return $tmpFile;
  }

  /**
   * Make Bytescale call.
   *
   * @param string $path
   *   The path.
   * @param array $queryString
   *   The query string.
   * @param string $method
   *   The method.
   * @param string $body
   *   Data to attach if POST/PUT/PATCH.
   * @param array $options
   *   Extra headers.
   * @param bool $basePath
   *   If the base path should be added.
   *
   * @return string|object
   *   The return response.
   */
  protected function makeRequest($path, array $queryString = [], $method = 'GET', $body = '', array $options = [], $basePath = TRUE) {
    // We can wait some.
    $options['connect_timeout'] = 30;
    $options['read_timeout'] = 30;
    // Don't let Guzzle die, just forward body and status.
    $options['http_errors'] = FALSE;
    // Headers.
    $options['headers']['Authorization'] = 'Bearer ' . $this->apiKey;

    if ($body) {
      $options['body'] = $body;
    }

    $newUrl = $basePath ? $this->basePath . $path : $path;
    $newUrl .= count($queryString) ? '?' . implode('&', $queryString) : '';

    $res = $this->client->request($method, $newUrl, $options);

    return $res->getBody();
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc