Browse Source

captions v2 (#826)

pull/834/head
Eli Zoller 4 years ago committed by GitHub
parent
commit
bdf99377e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      modules/islandora_audio/islandora_audio.module
  2. 28
      modules/islandora_audio/src/Plugin/Field/FieldFormatter/IslandoraFileAudioFormatter.php
  3. 28
      modules/islandora_audio/templates/islandora-file-audio.html.twig
  4. 15
      modules/islandora_video/islandora_video.module
  5. 95
      modules/islandora_video/src/Plugin/Field/FieldFormatter/IslandoraFileVideoFormatter.php
  6. 27
      modules/islandora_video/templates/islandora-file-video.html.twig
  7. 92
      src/Plugin/Field/FieldFormatter/IslandoraFileMediaFormatterBase.php
  8. 224
      src/Plugin/Field/FieldType/MediaTrackItem.php
  9. 173
      src/Plugin/Field/FieldWidget/MediaTrackWidget.php

15
modules/islandora_audio/islandora_audio.module

@ -30,3 +30,18 @@ function islandora_audio_help($route_name, RouteMatchInterface $route_match) {
default: default:
} }
} }
/**
* Implements hook_theme().
*/
function islandora_audio_theme() {
return [
'islandora_file_audio' => [
'variables' => [
'files' => [],
'tracks' => NULL,
'attributes' => NULL,
],
],
];
}

28
modules/islandora_audio/src/Plugin/Field/FieldFormatter/IslandoraFileAudioFormatter.php

@ -0,0 +1,28 @@
<?php
namespace Drupal\islandora_audio\Plugin\Field\FieldFormatter;
use Drupal\islandora\Plugin\Field\FieldFormatter\IslandoraFileMediaFormatterBase;
/**
* Plugin implementation of the 'file_audio' formatter.
*
* @FieldFormatter(
* id = "islandora_file_audio",
* label = @Translation("Audio with Captions"),
* description = @Translation("Display the file using an HTML5 audio tag."),
* field_types = {
* "file"
* }
* )
*/
class IslandoraFileAudioFormatter extends IslandoraFileMediaFormatterBase {
/**
* {@inheritdoc}
*/
public static function getMediaType() {
return 'audio';
}
}

28
modules/islandora_audio/templates/islandora-file-audio.html.twig

@ -0,0 +1,28 @@
{#
/**
* @file
* Default theme implementation to display the file entity as an audio tag.
*
* Available variables:
* - attributes: An array of HTML attributes, intended to be added to the
* audio tag.
* - files: And array of files to be added as sources for the audio tag. Each
* element is an array with the following elements:
* - file: The full file object.
* - source_attributes: An array of HTML attributes for to be added to the
* source tag.
*
* @ingroup themeable
*/
#}
<audio {{ attributes }}>
{% for file in files %}
<source {{ file.source_attributes }} />
{% if tracks %}
{% for track in tracks %}
<track {{ track.track_attributes }}
{% endfor %}
{% endif %}
{% endfor %}
</audio>

15
modules/islandora_video/islandora_video.module

@ -30,3 +30,18 @@ function islandora_video_help($route_name, RouteMatchInterface $route_match) {
default: default:
} }
} }
/**
* Implements hook_theme().
*/
function islandora_video_theme() {
return [
'islandora_file_video' => [
'variables' => [
'files' => [],
'tracks' => NULL,
'attributes' => NULL,
],
],
];
}

95
modules/islandora_video/src/Plugin/Field/FieldFormatter/IslandoraFileVideoFormatter.php

@ -0,0 +1,95 @@
<?php
namespace Drupal\islandora_video\Plugin\Field\FieldFormatter;
use Drupal\Core\Form\FormStateInterface;
use Drupal\islandora\Plugin\Field\FieldFormatter\IslandoraFileMediaFormatterBase;
/**
* Plugin implementation of the 'file_video' formatter.
*
* @FieldFormatter(
* id = "islandora_file_video",
* label = @Translation("Video with Captions"),
* description = @Translation("Display the file using an HTML5 video tag."),
* field_types = {
* "file"
* }
* )
*/
class IslandoraFileVideoFormatter extends IslandoraFileMediaFormatterBase {
/**
* {@inheritdoc}
*/
public static function getMediaType() {
return 'video';
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'muted' => FALSE,
'width' => 640,
'height' => 480,
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
return parent::settingsForm($form, $form_state) + [
'muted' => [
'#title' => $this->t('Muted'),
'#type' => 'checkbox',
'#default_value' => $this->getSetting('muted'),
],
'width' => [
'#type' => 'number',
'#title' => $this->t('Width'),
'#default_value' => $this->getSetting('width'),
'#size' => 5,
'#maxlength' => 5,
'#field_suffix' => $this->t('pixels'),
'#min' => 0,
'#required' => TRUE,
],
'height' => [
'#type' => 'number',
'#title' => $this->t('Height'),
'#default_value' => $this->getSetting('height'),
'#size' => 5,
'#maxlength' => 5,
'#field_suffix' => $this->t('pixels'),
'#min' => 0,
'#required' => TRUE,
],
];
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
$summary[] = $this->t('Muted: %muted', ['%muted' => $this->getSetting('muted') ? $this->t('yes') : $this->t('no')]);
$summary[] = $this->t('Size: %width x %height pixels', [
'%width' => $this->getSetting('width'),
'%height' => $this->getSetting('height'),
]);
return $summary;
}
/**
* {@inheritdoc}
*/
protected function prepareAttributes(array $additional_attributes = []) {
return parent::prepareAttributes(['muted'])
->setAttribute('width', $this->getSetting('width'))
->setAttribute('height', $this->getSetting('height'));
}
}

27
modules/islandora_video/templates/islandora-file-video.html.twig

@ -0,0 +1,27 @@
{#
/**
* @file
* Default theme implementation to display the file entity as a video tag.
*
* Available variables:
* - attributes: An array of HTML attributes, intended to be added to the
* video tag.
* - files: And array of files to be added as sources for the video tag. Each
* element is an array with the following elements:
* - file: The full file object.
* - source_attributes: An array of HTML attributes for to be added to the
* source tag.
*
* @ingroup themeable
*/
#}
<video {{ attributes }}>
{% for file in files %}
<source {{ file.source_attributes }} />
{% endfor %}
{% if tracks %}
{% for track in tracks %}
<track {{ track.track_attributes }}
{% endfor %}
{% endif %}
</video>

92
src/Plugin/Field/FieldFormatter/IslandoraFileMediaFormatterBase.php

@ -0,0 +1,92 @@
<?php
namespace Drupal\islandora\Plugin\Field\FieldFormatter;
use Drupal\file\Plugin\Field\FieldFormatter\FileMediaFormatterBase;
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Template\Attribute;
/**
* Extension of FileMediaFormatterBase that enables captions.
*/
abstract class IslandoraFileMediaFormatterBase extends FileMediaFormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
$source_files = $this->getSourceFiles($items, $langcode);
$track_files = $this->getTrackFiles($items, $langcode);
if (!empty($source_files)) {
$attributes = $this->prepareAttributes();
foreach ($source_files as $delta => $files) {
$elements[$delta] = [
'#theme' => $this->getPluginId(),
'#attributes' => $attributes,
'#files' => $files,
'#tracks' => isset($track_files[$delta]) ? $track_files[$delta] : [],
'#cache' => ['tags' => []],
];
$cache_tags = [];
foreach ($files as $file) {
$cache_tags = Cache::mergeTags($cache_tags, $file['file']->getCacheTags());
}
$elements[$delta]['#cache']['tags'] = $cache_tags;
}
}
return $elements;
}
/**
* Gets the track files with attributes.
*
* @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items
* The items.
* @param string $langcode
* The langcode.
*
* @return array
* Numerically indexed array, which again contains an associative array with
* the following key/values:
* - file => \Drupal\file\Entity\File
* - track_attributes => \Drupal\Core\Template\Attribute
*/
protected function getTrackFiles(EntityReferenceFieldItemListInterface $items, $langcode) {
$track_files = [];
$media_entity = $items->getParent()->getEntity();
$fields = $media_entity->getFields();
foreach ($fields as $key => $field) {
$definition = $field->getFieldDefinition();
if (method_exists($definition, 'get')) {
if ($definition->get('field_type') == 'media_track') {
// Extract the info for each track.
$entities = $field->referencedEntities();
$values = $field->getValue();
foreach ($entities as $delta => $file) {
$track_attributes = new Attribute();
$track_attributes
->setAttribute('src', $file->createFileUrl())
->setAttribute('srclang', $values[$delta]['srclang'])
->setAttribute('label', $values[$delta]['label'])
->setAttribute('kind', $values[$delta]['kind']);
if ($values[$delta]['default']) {
$track_attributes->setAttribute('default', 'default');
}
$track_files[0][] = [
'file' => $file,
'track_attributes' => $track_attributes,
];
}
}
}
}
return $track_files;
}
}

224
src/Plugin/Field/FieldType/MediaTrackItem.php

@ -0,0 +1,224 @@
<?php
namespace Drupal\islandora\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\file\Plugin\Field\FieldType\FileItem;
/**
* Plugin implementation of the 'media_track' field type.
*
* @FieldType(
* id = "media_track",
* label = @Translation("Media track"),
* description = @Translation("This field stores the ID of a media track file as an integer value."),
* category = @Translation("Reference"),
* default_widget = "media_track",
* default_formatter = "file_default",
* column_groups = {
* "file" = {
* "label" = @Translation("File"),
* "columns" = {
* "target_id"
* },
* "require_all_groups_for_translation" = TRUE
* },
* "label" = {
* "label" = @Translation("Track label"),
* "translatable" = FALSE,
* },
* "kind" = {
* "label" = @Translation("Kind"),
* "translatable" = FALSE
* },
* "srclang" = {
* "label" = @Translation("SRC Language"),
* "translatable" = FALSE
* },
* "default" = {
* "label" = @Translation("Default"),
* "translatable" = FALSE
* },
* },
* list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList",
* constraints = {"ReferenceAccess" = {}, "FileValidation" = {}}
* )
*/
class MediaTrackItem extends FileItem {
/**
* {@inheritdoc}
*/
public static function defaultFieldSettings() {
$settings = [
'file_extensions' => 'vtt',
'languages' => 'installed',
] + parent::defaultFieldSettings();
unset($settings['description_field']);
return $settings;
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
return [
'columns' => [
'target_id' => [
'description' => 'The ID of the file entity.',
'type' => 'int',
'unsigned' => TRUE,
],
'label' => [
'description' => "Label of track, for the track's 'label' attribute.",
'type' => 'varchar',
'length' => 128,
],
'kind' => [
'description' => "Type of track, for the track's 'kind' attribute.",
'type' => 'varchar',
'length' => 20,
],
'srclang' => [
'description' => "Language of track, for the track's 'srclang' attribute.",
'type' => 'varchar',
'length' => 20,
],
'default' => [
'description' => "Flag to indicate whether to use this as the default track of this kind.",
'type' => 'int',
'size' => 'tiny',
'unsigned' => TRUE,
'default' => 0,
],
],
'indexes' => [
'target_id' => ['target_id'],
],
'foreign keys' => [
'target_id' => [
'table' => 'file_managed',
'columns' => ['target_id' => 'fid'],
],
],
];
}
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties = parent::propertyDefinitions($field_definition);
unset($properties['display']);
unset($properties['description']);
$properties['label'] = DataDefinition::create('string')
->setLabel(t('Label'))
->setDescription(t("Label of track, for the track's 'label' attribute."));
$properties['kind'] = DataDefinition::create('string')
->setLabel(t('Track kind'))
->setDescription(t("Type of track, for the track's 'kind' attribute."));
$properties['srclang'] = DataDefinition::create('string')
->setLabel(t('SRC Language'))
->setDescription(t("Language of track, for the track's 'srclang' attribute."));
$properties['default'] = DataDefinition::create('boolean')
->setLabel(t('Default'))
->setDescription(t("Flag to indicate whether to use this as the default track of this kind."));
return $properties;
}
/**
* {@inheritdoc}
*/
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
$element = [];
$scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE);
$element['uri_scheme'] = [
'#type' => 'radios',
'#title' => t('Upload destination'),
'#options' => $scheme_options,
'#default_value' => $this->getSetting('uri_scheme'),
'#description' => 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) {
// Get base form from FileItem.
$element = parent::fieldSettingsForm($form, $form_state);
$settings = $this->getSettings();
// Remove the description option.
unset($element['description_field']);
$element['languages'] = [
'#type' => 'radios',
'#title' => $this->t('List all languages'),
'#description' => $this->t('Allow the user to select all languages or only the currently installed languages?'),
'#options' => [
'all' => $this->t('All Languages'),
'installed' => $this->t('Currently Installed Languages'),
],
'#default_value' => $settings['languages'] ?: 'installed',
];
return $element;
}
/**
* {@inheritdoc}
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
// @todo This will need to generate a text file, containing a sequence of
// timestamps and nonsense text. Include some gap periods where nothing is
// displayed.
// See the ImageItem::generateSampleValue() for how the files are saved.
// The part about "Generate a max of 5 different images" is a good idea
// here too. We only need a WebVTT few files.
// See one of the text item implementation for how to generate nonsense.
// Pseudocode plan...
// $sample_file_text = 'WEBVTT'; // start of file.
// for ($i = 0; $ < $utterances; $i++) {
// $sample_file_text += \n\n
// $timestamp += 3-10seconds // start of display
// $sample_file_text += $timestamp
// $timestamp += 3-10seconds // end of display
// $sample_file_text += $timestamp
// $sample_file_text += randomText()
// }
// $file = file_write($sample_file_text, 'random_filename.vtt');
$values = [
'target_id' => $file->id(),
// Randomize the rest of these...
'label' => '',
'kind' => '',
'srclang' => '',
// Careful with this one, complex validation.
'default' => '',
];
return $values;
}
/**
* {@inheritdoc}
*/
public function isDisplayed() {
return TRUE;
}
}

173
src/Plugin/Field/FieldWidget/MediaTrackWidget.php

@ -0,0 +1,173 @@
<?php
namespace Drupal\islandora\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManager;
use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
/**
* Plugin implementation of the 'media_track' widget.
*
* @FieldWidget(
* id = "media_track",
* label = @Translation("Media Track"),
* field_types = {
* "media_track"
* }
* )
*/
class MediaTrackWidget extends FileWidget {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'progress_indicator' => 'throbber',
] + parent::defaultSettings();
}
/**
* Overrides FileWidget::formMultipleElements().
*
* Special handling for draggable multiple widgets and 'add more' button.
*/
protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
$elements = parent::formMultipleElements($items, $form, $form_state);
$cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
$file_upload_help = [
'#theme' => 'file_upload_help',
'#description' => '',
'#upload_validators' => $elements[0]['#upload_validators'],
'#cardinality' => $cardinality,
];
if ($cardinality == 1) {
// If there's only one field, return it as delta 0.
if (empty($elements[0]['#default_value']['fids'])) {
$file_upload_help['#description'] = $this->getFilteredDescription();
$elements[0]['#description'] = \Drupal::service('renderer')->renderPlain($file_upload_help);
}
}
else {
$elements['#file_upload_description'] = $file_upload_help;
}
return $elements;
}
/**
* {@inheritDoc}
*
* Add the field settings so they can be used in the process method.
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element = parent::formElement($items, $delta, $element, $form, $form_state);
$element['#field_settings'] = $this->getFieldSettings();
return $element;
}
/**
* Form API callback: Processes a media_track field element.
*
* Expands the media_track type to include the alt and title fields.
*
* This method is assigned as a #process callback in formElement() method.
*/
public static function process($element, FormStateInterface $form_state, $form) {
$item = $element['#value'];
$item['fids'] = $element['fids']['#value'];
$element['label'] = [
'#title' => t('Label'),
'#type' => 'textfield',
'#default_value' => isset($item['label']) ? $item['label'] : '',
'#description' => t('Label for the track file.'),
'#maxlength' => 128,
'#access' => (bool) $item['fids'],
];
$element['kind'] = [
'#title' => t('Kind'),
'#type' => 'select',
'#description' => t('The kind of media track.'),
'#options' => [
'subtitles' => t('Subtitles'),
'captions' => t('Captions'),
'descriptions' => t('Descriptions'),
'chapters' => t('Chapters'),
'metadata' => t('Metadata'),
],
'#default_value' => isset($item['kind']) ? $item['kind'] : '',
'#access' => (bool) $item['fids'],
];
$srclang_options = [];
if ($element['#field_settings']['languages'] == 'all') {
// Need to list all languages.
$languages = LanguageManager::getStandardLanguageList();
foreach ($languages as $key => $language) {
if ($language[0] == $language[1]) {
// Both the native language name and the English language name are
// the same, so only show one of them.
$srclang_options[$key] = $language[0];
}
else {
// The native language name and English language name are different
// so show both of them.
$srclang_options[$key] = t('@lang0 / @lang1', [
'@lang0' => $language[0],
'@lang1' => $language[1],
]);
}
}
}
else {
// Only list the installed languages.
$languages = \Drupal::languageManager()->getLanguages();
foreach ($languages as $key => $language) {
$srclang_options[$key] = $language->getName();
}
}
$element['srclang'] = [
'#title' => t('SRC Language'),
'#description' => t('Choose from one of the installed languages.'),
'#type' => 'select',
'#options' => $srclang_options,
'#default_value' => isset($item['srclang']) ? $item['srclang'] : '',
'#maxlength' => 20,
'#access' => (bool) $item['fids'],
'#element_validate' => [[get_called_class(), 'validateRequiredFields']],
];
// @see https://www.w3.org/TR/html/semantics-embedded-content.html#elementdef-track
$element['default'] = [
'#type' => 'checkbox',
'#title' => t('Default track'),
'#default_value' => isset($item['default']) ? $item['default'] : '',
'#description' => t('Use this as the default track of this kind.'),
'#access' => (bool) $item['fids'],
];
return parent::process($element, $form_state, $form);
}
/**
* Validate callback for kind/srclang/label/default.
*
* This is separated in a validate function instead of a #required flag to
* avoid being validated on the process callback.
* The 'default' track has complex validation, see HTML5.2 for details.
*/
public static function validateRequiredFields($element, FormStateInterface $form_state) {
// Only do validation if the function is triggered from other places than
// the image process form.
$triggering_element = $form_state->getTriggeringElement();
if (!empty($triggering_element['#submit']) && in_array('file_managed_file_submit', $triggering_element['#submit'], TRUE)) {
$form_state->setLimitValidationErrors([]);
}
}
}
Loading…
Cancel
Save