elevenlabs_field-1.0.0-beta7/src/Plugin/Field/FieldType/ElevenLabsGenerationItem.php

src/Plugin/Field/FieldType/ElevenLabsGenerationItem.php
<?php

namespace Drupal\elevenlabs_field\Plugin\Field\FieldType;

use Drupal\Component\Utility\Random;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\file\Plugin\Field\FieldType\FileItem;

/**
 * Defines the 'elevenlabs' field type.
 *
 * @FieldType(
 *   id = "elevenlabs",
 *   label = @Translation("ElevenLabs"),
 *   category = @Translation("Media"),
 *   default_widget = "elevenlabs",
 *   default_formatter = "elevenlabs_default",
 *   column_groups = {
 *     "file" = {
 *       "label" = @Translation("File"),
 *       "columns" = {
 *         "target_id"
 *       },
 *       "require_all_groups_for_translation" = TRUE
 *     },
 *     "text" = {
 *       "label" = @Translation("Alt"),
 *       "translatable" = FALSE
 *     },
 *     "speaker" = {
 *       "label" = @Translation("Title"),
 *       "translatable" = FALSE
 *     },
 *     "playing_time" = {
 *       "label" = @Translation("Alt"),
 *       "translatable" = FALSE
 *     },
 *     "start_time" = {
 *       "label" = @Translation("Title"),
 *       "translatable" = FALSE
 *     },
 *     "history_item_id" = {
 *       "label" = @Translation("Alt"),
 *       "translatable" = FALSE
 *     },
 *     "model_id" = {
 *       "label" = @Translation("Title"),
 *       "translatable" = FALSE
 *     },
 *     "stability" = {
 *       "label" = @Translation("Alt"),
 *       "translatable" = FALSE
 *     },
 *     "similarity_boost" = {
 *       "label" = @Translation("Title"),
 *       "translatable" = FALSE
 *     },
 *     "style_exaggeration" = {
 *       "label" = @Translation("Title"),
 *       "translatable" = FALSE
 *     },
 *     "speaker_boost" = {
 *       "label" = @Translation("Title"),
 *       "translatable" = FALSE
 *     },
 *   },
 *   list_class = "\Drupal\elevenlabs_field\Plugin\Field\FieldType\ElevenLabsFieldItemList",
 *   constraints = {"ReferenceAccess" = {}, "FileValidation" = {}}
 * )
 */
class ElevenLabsGenerationItem extends FileItem {

  /**
   * {@inheritdoc}
   */
  public static function mainPropertyName() {
    return 'target_id';
  }

  /**
   * {@inheritdoc}
   */
  public static function defaultStorageSettings() {
    $storageSettings = parent::defaultStorageSettings();
    $storageSettings['target_type'] = 'file';
    unset($storageSettings['display_field']);
    unset($storageSettings['display_default']);
    return $storageSettings;
  }

  /**
   * {@inheritdoc}
   */
  public static function defaultFieldSettings() {
    $settings = parent::defaultFieldSettings();
    $settings['save_audio'] = '';
    $settings['concatenate'] = FALSE;
    $settings['batch'] = FALSE;
    $settings['file_extensions'] = 'mp3';
    $settings['file_name'] = 'elevenlabs';
    $settings['concatenate_advanced']['intro'] = FALSE;
    $settings['concatenate_advanced']['intro_details']['intro_field'] = '';
    $settings['concatenate_advanced']['intro_details']['intro_file'] = 0;
    $settings['concatenate_advanced']['intro_details']['intro_volume'] = 100;
    $settings['concatenate_advanced']['outro'] = FALSE;
    $settings['concatenate_advanced']['outro_details']['outro_field'] = '';
    $settings['concatenate_advanced']['outro_details']['outro_file'] = 0;
    $settings['concatenate_advanced']['outro_details']['outro_volume'] = 100;
    $settings['concatenate_advanced']['soundscape'] = FALSE;
    $settings['concatenate_advanced']['soundscape_details']['soundscape_field'] = '';
    $settings['concatenate_advanced']['soundscape_details']['soundscape_file'] = 0;
    $settings['concatenate_advanced']['soundscape_details']['soundscape_cut_time'] = 0;
    $settings['concatenate_advanced']['soundscape_details']['soundscape_volume'] = 60;
    $settings['concatenate_advanced']['soundscape_details']['soundscape_start_wait'] = 3000;
    $settings['concatenate_advanced']['soundscape_details']['soundscape_fade_in'] = 0;
    $settings['concatenate_advanced']['soundscape_details']['soundscape_prolong'] = 3000;
    $settings['concatenate_advanced']['soundscape_details']['soundscape_fade_out'] = 3000;
    return $settings;
  }

  /**
   * {@inheritdoc}
   */
  public function isEmpty() {
    if (empty($this->text)) {
      return TRUE;
    }
    return FALSE;
  }

  /**
   * {@inheritDoc}
   */
  public function hasNewEntity() {
    return !$this->isEmpty() && $this->target_id === NULL;
  }

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties = parent::propertyDefinitions($field_definition);
    unset($properties['display']);
    unset($properties['description']);
    $properties['text'] = DataDefinition::create('string')
      ->setLabel(t('Text'));
    $properties['speaker'] = DataDefinition::create('string')
      ->setLabel(t('Speaker'));
    $properties['playing_time'] = DataDefinition::create('integer')
      ->setLabel(t('Playing Time'));
    $properties['start_time'] = DataDefinition::create('integer')
      ->setLabel(t('Start Time'));
    $properties['history_item_id'] = DataDefinition::create('string')
      ->setLabel(t('History Item ID'));
    $properties['model_id'] = DataDefinition::create('string')
      ->setLabel(t('Model ID'));
    $properties['stability'] = DataDefinition::create('integer')
      ->setLabel(t('Stability'));
    $properties['similarity_boost'] = DataDefinition::create('integer')
      ->setLabel(t('Similarity Boost'));
    $properties['style_exaggeration'] = DataDefinition::create('integer')
      ->setLabel(t('Style Exaggeration'));
    $properties['speaker_boost'] = DataDefinition::create('integer')
      ->setLabel(t('Speaker Boost'));

    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public function getValue() {
    if ($this->target_id) {
      parent::getValue();
      $file = \Drupal::entityTypeManager()->getStorage('file')->load($this->target_id);
      $this->set('entity', $file);
      return [
        'target_id' => $this->target_id,
        'text' => $this->text,
        'speaker' => $this->speaker,
        'playing_time' => $this->playing_time,
        'start_time' => $this->start_time,
        'history_item_id' => $this->history_item_id,
        'model_id' => $this->model_id,
        'stability' => $this->stability,
        'similarity_boost' => $this->similarity_boost,
        'style_exaggeration' => $this->style_exaggeration,
        'speaker_boost' => $this->speaker_boost,
      ];
    }
  }

  /**
   * {@inheritdoc}
   */
  public function preSave() {
    $settings = $this->getSettings();
    // Get original values to calculate if we need to generate new audio.
    $elevenLabsMap = [];
    // Only possible when there is an update.
    if (!empty($this->getEntity()->original)) {
      foreach ($this->getEntity()->original->get($this->getFieldDefinition()->getName())->getValue() as $value) {
        // Only count started processes.
        if (!empty($value['history_item_id'])) {
          $elevenLabsMap[$value['history_item_id']] = [
            'text' => $value['text'],
            'speaker' => $value['speaker'],
            'target_id' => $value['target_id'],
          ];
        }
      }
    }

    // If no elevenlabs id exists, generate audio.
    $generateAudio = FALSE;
    // Recopy to audio fields if something changed.
    if (empty($this->history_item_id)) {
      $generateAudio = TRUE;
    }
    // If the text or speaker changed, but not the file.
    elseif (
      isset($elevenLabsMap[$this->history_item_id]) &&
      ($this->text != $elevenLabsMap[$this->history_item_id]['text'] ||
      $this->speaker != $elevenLabsMap[$this->history_item_id]['speaker'])
    ) {
      $generateAudio = TRUE;
    }

    if ($generateAudio && !empty($this->text)) {
      // Batch jobs gets a temporary id.
      if ($settings['batch'] && $this->getName() != 0) {
        $this->set('target_id', 0);
      }
      else {
        $generator = \Drupal::service('elevenlabs_field.generator_service');
        $model = $generator->validateModel($this->model_id, $this->getEntity());
        $data = $generator->generateFile($this->text, $this->speaker, $this->getFieldDefinition(), $model, [
          'stability' => ($this->stability / 100),
          'similarity_boost' => ($this->similarity_boost / 100),
          'style' => ($this->style_exaggeration / 100),
          'use_speaker_boost' => $this->speaker_boost ? TRUE : FALSE,
        ]);
        if (!empty($data['file'])) {
          // Remove old data if exists.
          if ($this->target_id) {
            $generator->removeFile($this->target_id);
          }
          $this->entity = $data['file'];
          $this->set('target_id', $data['file']->id());
          $this->set('history_item_id', $data['history_item_id']);
        }
      }
    }
    elseif ($this->target_id) {
      $file = \Drupal::entityTypeManager()->getStorage('file')->load($this->target_id);
      $this->set('entity', $file);
    }

    if ($settings['save_audio']) {
      _elevenlabs_field_needs_copying($this->getEntity(), [
        'type' => $settings['concatenate'] ? 'concatenate' : 'save',
        'field' => $settings['save_audio'],
        'batch' => $settings['batch'],
        'original_field' => $this->getFieldDefinition()->getName(),
      ]);
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {

    $columns = [
      'text' => [
        'type' => 'text',
        'size' => 'big',
      ],
      'speaker' => [
        'type' => 'varchar',
        'length' => 255,
      ],
      'playing_time' => [
        'type' => 'int',
        'size' => 'normal',
      ],
      'start_time' => [
        'type' => 'int',
        'size' => 'normal',
      ],
      'history_item_id' => [
        'type' => 'varchar',
        'length' => 255,
      ],
      'model_id' => [
        'type' => 'varchar',
        'length' => 255,
      ],
      'target_id' => [
        'description' => 'The ID of the file entity.',
        'type' => 'int',
        'unsigned' => TRUE,
      ],
      'stability' => [
        'type' => 'int',
        'size' => 'normal',
      ],
      'similarity_boost' => [
        'type' => 'int',
        'size' => 'normal',
      ],
      'style_exaggeration' => [
        'type' => 'int',
        'size' => 'normal',
      ],
      'speaker_boost' => [
        'type' => 'int',
        'size' => 'normal',
      ],
    ];

    $schema = [
      'columns' => $columns,
      'indexes' => [
        'history_item_id' => ['history_item_id'],
        'target_id' => ['target_id'],
      ],
      'foreign keys' => [
        'target_id' => [
          'table' => 'file_managed',
          'columns' => ['target_id' => 'fid'],
        ],
      ],
    ];

    return $schema;
  }

  /**
   * {@inheritdoc}
   */
  public static function generateSampleValue(FieldDefinitionInterface $field_definition) {

    $random = new Random();

    $values['text'] = $random->paragraphs(5);

    $values['speaker'] = '21m00Tcm4TlvDq8ikWAM';

    $values['history_item_id'] = '21m00Tcm4TlvDq8ikWAM';

    $values['playing_time'] = mt_rand(-1000, 1000);

    $values['start_time'] = mt_rand(-1000, 1000);

    $values['model_id'] = 'eleven_monolingual_v1';

    $dirname = 'public://elevenlabs';
    \Drupal::service('file_system')->prepareDirectory($dirname, FileSystemInterface::CREATE_DIRECTORY);
    $destination = $dirname . '/' . $random->name(10, TRUE) . '.mp4';
    $data = $random->paragraphs(3);
    /** @var \Drupal\file\FileRepositoryInterface $file_repository */
    $file_repository = \Drupal::service('file.repository');
    $file = $file_repository->writeData($data, $destination, FileSystemInterface::EXISTS_ERROR);
    $values['target_id'] = $file->id();

    return $values;
  }

  /**
   * {@inheritdoc}
   */
  public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
    $element = [];

    // We need the field-level 'default_image' setting, and $this->getSettings()
    // will only provide the instance-level one, so we need to explicitly fetch
    // the field.
    $settings = $this->getFieldDefinition()->getFieldStorageDefinition()->getSettings();

    $scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE);
    $element['uri_scheme'] = [
      '#type' => 'radios',
      '#title' => $this->t('Upload destination'),
      '#options' => $scheme_options,
      '#default_value' => $settings['uri_scheme'],
      '#description' => $this->t('Select where the final files should be stored. Private file storage has significantly more overhead than public files, but allows restricted access to files within this field.'),
    ];

    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
    $element = [];
    $settings = $this->getSettings();

    $element['file_directory'] = [
      '#type' => 'textfield',
      '#title' => $this->t('File directory'),
      '#default_value' => $settings['file_directory'],
      '#description' => $this->t('Optional subdirectory within the upload destination where files will be stored. Do not include preceding or trailing slashes.'),
      '#weight' => -1,
    ];

    $element['file_name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('File name'),
      '#default_value' => $settings['file_name'],
      '#description' => $this->t('Optional file name for the mp3 files created.'),
      '#weight' => 0,
    ];

    $element['save_audio'] = [
      '#type' => 'select',
      '#title' => $this->t('Re-Save in Audio field'),
      '#default_value' => $settings['save_audio'],
      '#description' => $this->t('This will be the field to re-save the audio to. Only fields that allow mp3 and the same amount of fields or more will work.'),
      '#options' => $this->getAudioFields($form),
    ];

    $element['batch'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable Batching'),
      '#description' => $this->t('On saving this would batch each audio generation. Do this if you have timeouts.'),
      '#default_value' => $settings['batch'],
    ];

    $element['concatenate'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Concatenate File in FFmpeg'),
      '#description' => $this->t('If FFmpeg is available on the server, you can concatenate a long discussion into one file.'),
      '#default_value' => $settings['concatenate'],
      '#attributes' => [
        'disabled' => $this->checkFfmpeg(),
      ],
      '#states' => [
        'visible' => [
          ':input[name="settings[save_audio]"]' => [
            "!value" => "",
          ],
        ],
      ],
    ];

    $element['concatenate_advanced'] = [
      '#type' => 'details',
      '#title' => $this->t('Concatenate Settings'),
      '#open' => TRUE,
      '#states' => [
        'visible' => [
          ':input[name="settings[concatenate]"]' => [
            'checked' => TRUE,
          ],
        ],
      ],
    ];

    $element['concatenate_advanced']['intro'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Intro'),
      '#description' => $this->t('Add an intro to the created file.'),
      '#default_value' => $settings['concatenate_advanced']['intro'],
    ];

    $element['concatenate_advanced']['intro_details'] = [
      '#type' => 'details',
      '#title' => $this->t('Intro Details'),
      '#open' => TRUE,
      '#states' => [
        'visible' => [
          ':input[name="settings[concatenate_advanced][intro]"]' => [
            'checked' => TRUE,
          ],
        ],
      ],
    ];

    $element['concatenate_advanced']['intro_details']['intro_file'] = [
      '#type' => 'managed_file',
      '#title' => $this->t('Intro Music'),
      '#description' => $this->t('Intro before the main text.'),
      '#default_value' => $settings['concatenate_advanced']['intro_details']['intro_file'] ?? NULL,
      '#upload_location' => 'public://intro/',
      '#element_validate' => [
        '\Drupal\file\Element\ManagedFile::validateManagedFile',
      ],
      '#upload_validators' => $this->getUploadValidators(),
    ];

    $element['concatenate_advanced']['intro_details']['intro_field'] = [
      '#type' => 'select',
      '#title' => $this->t('Use Content Field'),
      '#default_value' => $settings['concatenate_advanced']['intro_details']['intro_field'],
      '#description' => $this->t('This will be the field with the Intro.'),
      '#options' => $this->getAudioFields($form, '-- No Soudscape Field --'),
    ];

    $element['concatenate_advanced']['intro_details']['intro_volume'] = [
      '#type' => 'number',
      '#title' => $this->t('Intro Volume'),
      '#min' => 1,
      '#max' => 100,
      '#description' => $this->t('The volume to play the intro in.'),
      '#default_value' => $settings['concatenate_advanced']['intro_details']['intro_volume'],
    ];

    $element['concatenate_advanced']['outro'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Outro'),
      '#description' => $this->t('Add an outro to the created file.'),
      '#default_value' => $settings['concatenate_advanced']['outro'],
    ];

    $element['concatenate_advanced']['outro_details'] = [
      '#type' => 'details',
      '#title' => $this->t('Outro Details'),
      '#open' => TRUE,
      '#states' => [
        'visible' => [
          ':input[name="settings[concatenate_advanced][outro]"]' => [
            'checked' => TRUE,
          ],
        ],
      ],
    ];

    $element['concatenate_advanced']['outro_details']['outro_file'] = [
      '#type' => 'managed_file',
      '#title' => $this->t('Outro Music'),
      '#description' => $this->t('Outro after the main text.'),
      '#default_value' => $settings['concatenate_advanced']['outro_details']['outro_file'] ?? NULL,
      '#upload_location' => 'public://outro/',
      '#element_validate' => [
        '\Drupal\file\Element\ManagedFile::validateManagedFile',
      ],
      '#upload_validators' => $this->getUploadValidators(),
    ];

    $element['concatenate_advanced']['outro_details']['outro_field'] = [
      '#type' => 'select',
      '#title' => $this->t('Use Content Field'),
      '#default_value' => $settings['concatenate_advanced']['outro_details']['outro_field'],
      '#description' => $this->t('This will be the field with the outro.'),
      '#options' => $this->getAudioFields($form, '-- No Soudscape Field --'),
    ];

    $element['concatenate_advanced']['outro_details']['outro_volume'] = [
      '#type' => 'number',
      '#title' => $this->t('Outro Volume'),
      '#min' => 1,
      '#max' => 100,
      '#description' => $this->t('The volume to play the outr in.'),
      '#default_value' => $settings['concatenate_advanced']['outro_details']['outro_volume'],
    ];

    $element['concatenate_advanced']['soundscape'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Soundscape'),
      '#description' => $this->t('Add a soundscape under the talking file.'),
      '#default_value' => $settings['concatenate_advanced']['soundscape'],
    ];

    $element['concatenate_advanced']['soundscape_details'] = [
      '#type' => 'details',
      '#title' => $this->t('Soundscape Details'),
      '#open' => TRUE,
      '#states' => [
        'visible' => [
          ':input[name="settings[concatenate_advanced][soundscape]"]' => [
            'checked' => TRUE,
          ],
        ],
      ],
    ];

    $element['concatenate_advanced']['soundscape_details']['soundscape_file'] = [
      '#type' => 'managed_file',
      '#title' => $this->t('Soundscape Music'),
      '#description' => $this->t('Soundscape under the main text.'),
      '#default_value' => $settings['concatenate_advanced']['soundscape_details']['soundscape_file'] ?? NULL,
      '#upload_location' => 'public://soundscape/',
      '#element_validate' => [
        '\Drupal\file\Element\ManagedFile::validateManagedFile',
      ],
      '#upload_validators' => $this->getUploadValidators(),
    ];

    $element['concatenate_advanced']['soundscape_details']['soundscape_field'] = [
      '#type' => 'select',
      '#title' => $this->t('Use Content Field'),
      '#default_value' => $settings['concatenate_advanced']['soundscape_details']['soundscape_field'],
      '#description' => $this->t('This will be the field with the soundscape.'),
      '#options' => $this->getAudioFields($form, '-- No Soudscape Field --'),
    ];

    $element['concatenate_advanced']['soundscape_details']['soundscape_volume'] = [
      '#type' => 'number',
      '#title' => $this->t('Soundscape Volume'),
      '#min' => 1,
      '#max' => 100,
      '#description' => $this->t('The volume to play the soundscape in.'),
      '#default_value' => $settings['concatenate_advanced']['soundscape_details']['soundscape_volume'],
    ];

    $element['concatenate_advanced']['soundscape_details']['soundscape_cut_time'] = [
      '#type' => 'number',
      '#title' => $this->t('Soundscape Playing Length'),
      '#description' => $this->t('The amount of soundscape to play in total. 0 mean the entire thing.'),
      '#default_value' => $settings['concatenate_advanced']['soundscape_details']['soundscape_cut_time'],
    ];

    $element['concatenate_advanced']['soundscape_details']['soundscape_start_wait'] = [
      '#type' => 'number',
      '#title' => $this->t('Soundscape Start Padding'),
      '#description' => $this->t('The amount of soundscape to play before the talk starts.'),
      '#default_value' => $settings['concatenate_advanced']['soundscape_details']['soundscape_start_wait'],
    ];

    $element['concatenate_advanced']['soundscape_details']['soundscape_fade_in'] = [
      '#type' => 'number',
      '#title' => $this->t('Soundscape Fade In'),
      '#description' => $this->t('The length of a fade in on the soundscape in milliseconds. 0 means no fade in.'),
      '#default_value' => $settings['concatenate_advanced']['soundscape_details']['soundscape_fade_in'],
    ];

    $element['concatenate_advanced']['soundscape_details']['soundscape_prolong'] = [
      '#type' => 'number',
      '#title' => $this->t('Soundscape Prolonging'),
      '#description' => $this->t('The amount of soundscape to play after the talk stops in milliseconds.'),
      '#default_value' => $settings['concatenate_advanced']['soundscape_details']['soundscape_prolong'],
    ];

    $element['concatenate_advanced']['soundscape_details']['soundscape_fade_out'] = [
      '#type' => 'number',
      '#title' => $this->t('Soundscape Fade Out'),
      '#description' => $this->t('The length of a fade out on the soundscape in milliseconds. 0 means no fade out.'),
      '#default_value' => $settings['concatenate_advanced']['soundscape_details']['soundscape_fade_out'],
    ];

    return $element;
  }

  /**
   * Checks if FFMpeg is available on the server.
   *
   * @return bool
   *   True or false.
   */
  private function checkFfmpeg() {
    $command = (PHP_OS == 'WINNT') ? 'where ffmpeg' : 'which ffmpeg';
    $result = shell_exec($command);
    return !$result;
  }

  /**
   * Get the file field to use.
   *
   * @param array $form
   *   The form.
   * @param string $message
   *   The no message in the fields.
   *
   * @return array
   *   Options for the audio field generation.
   */
  private function getAudioFields(array $form, $message = '-- No audio generation --') {
    $options[''] = '-- No audio generation --';
    /** @var \Drupal\Core\Field\FieldDefinitionInterface */
    foreach ($form['#entity']->getFieldDefinitions() as $fieldDefinition) {
      if ($fieldDefinition->getType() == 'file' && ($fieldDefinition->getFieldStorageDefinition()->getCardinality() == -1 ||
        $fieldDefinition->getFieldStorageDefinition()->getCardinality() > $this->getFieldDefinition()->getFieldStorageDefinition()->getCardinality())) {
        $config = $fieldDefinition->getConfig($form['#entity']->bundle())->getSettings();
        $extensions = preg_split('/ (,| ) /', $config['file_extensions']);
        if (in_array('mp3', $extensions)) {
          $options[$fieldDefinition->getName()] = $fieldDefinition->getLabel();
        }
      }
    }
    return $options;
  }

}

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

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