diff --git a/README.md b/README.md
index b280482..103d771 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,14 @@
 # Drupal 7 to 8 media migration
 
-This module implements a migration path for Drupal 7 sites
-using media module to Drupal 8.
+This module implements a migration path for Drupal 7 sites using media module to
+Drupal 8|9.
 
 It performs the following:
 
-* For media bundles which exist in the destination, it creates and
-  attaches its fields along with their configuration.
-* Maps Drupal 7 file entities to their respective media ones in
-  content migrations.
+* For media bundles which exist in the destination, it creates and attaches its
+  fields along with their configuration.
+* Maps Drupal 7 file entities to their respective media ones in content
+  migrations.
 * Transforms image fields to media image fields.
 * Transforms Media WYSIWYG's `media_filter` tokens into `entity_embed` or
   `media_embed` ones.
@@ -60,18 +60,6 @@ install the Entity Embed module.
 You also have to create and/or configure the custom media view modes __with the
 same machine names__ that the source site used BEFORE running the migration.
 
-## Before running the below commands
-
-1. Install media module in Drupal 8.
-
-2. At the moment this module cannot create media bundles for you because
-  I (juampynr) could not figure out how to map a file type source in
-  Drupal 7 with a media source in Drupal 8. Therefore, if your
-  Drupal 7 project has other file types apart from the default ones,
-  configure them in Drupal 8 and respect the entity identifiers. Then
-  this module will be able to create and attach the fields plus
-  prepare the content migration. 
-
 ## Usage
 
 The following requires the `migrate_upgrade` module.
@@ -113,4 +101,5 @@ drush migrate:import --tag=Content --execute-dependencies
 ```
 
 The result of the above command should be all the media content migrated into
-media entities plus any media_wysiwyg tokens transformed into entity_embed ones.
+media entities plus any `media_wysiwyg` tokens transformed into `entity_embed`
+or `media_embed` ones.
diff --git a/media_migration.services.yml b/media_migration.services.yml
index 69de192..6cf829d 100644
--- a/media_migration.services.yml
+++ b/media_migration.services.yml
@@ -28,3 +28,7 @@ services:
   plugin.manager.file_dealer:
     class: Drupal\media_migration\FileDealerManager
     parent: default_plugin_manager
+
+  plugin.manager.file_entity_dealer:
+    class: Drupal\media_migration\FileEntityDealerManager
+    parent: default_plugin_manager
diff --git a/migrations/d7_file_entity.yml b/migrations/d7_file_entity.yml
index 6050f89..c3eb669 100644
--- a/migrations/d7_file_entity.yml
+++ b/migrations/d7_file_entity.yml
@@ -4,7 +4,7 @@ audit: true
 migration_tags:
   - Drupal 7
   - Content
-deriver: Drupal\media_migration\Plugin\migrate\D7FileEntityItemDeriver
+deriver: Drupal\media_migration\Plugin\migrate\D7FileEntityDeriver
 source:
   plugin: d7_file_entity_item
 process:
@@ -17,7 +17,7 @@ process:
       method: process
   mid: fid
   uid: uid
-  bundle: type
+  bundle: bundle
   name: filename
   created: timestamp
   changed: timestamp
@@ -27,6 +27,5 @@ destination:
 migration_dependencies:
   required:
     - d7_file_entity_type
-    - d7_field_instance
     - d7_file
     - d7_user
diff --git a/migrations/d7_file_entity_formatter.yml b/migrations/d7_file_entity_formatter.yml
new file mode 100644
index 0000000..01b15bd
--- /dev/null
+++ b/migrations/d7_file_entity_formatter.yml
@@ -0,0 +1,23 @@
+id: d7_file_entity_formatter
+label: File Entity media source field formatter
+migration_tags:
+  - Drupal 7
+  - Configuration
+deriver: Drupal\media_migration\Plugin\migrate\D7FileEntityConfigDeriver
+source:
+  plugin: d7_file_entity_field_formatter
+  constants:
+    entity_type_id: media
+    view_mode: default
+process:
+  entity_type: 'constants/entity_type_id'
+  bundle: bundle
+  view_mode: 'constants/view_mode'
+  field_name: field_name
+  hidden: hidden
+  options: options
+destination:
+  plugin: component_entity_display
+migration_dependencies:
+  required:
+    - d7_file_entity_source_field_config
diff --git a/migrations/d7_file_entity_source_field.yml b/migrations/d7_file_entity_source_field.yml
new file mode 100644
index 0000000..4dbe184
--- /dev/null
+++ b/migrations/d7_file_entity_source_field.yml
@@ -0,0 +1,30 @@
+id: d7_file_entity_source_field
+label: Media source field storage configuration
+migration_tags:
+  - Drupal 7
+  - Configuration
+deriver: Drupal\media_migration\Plugin\migrate\D7FileEntityConfigDeriver
+source:
+  plugin: d7_file_entity_source_field_storage
+  constants:
+    entity_type_id: media
+    status: true
+    langcode: und
+    cardinality: 1
+process:
+  field_name: source_field_name
+  status: 'constants/status'
+  langcode: 'constants/langcode'
+  entity_type: constants/entity_type_id
+  type: field_type
+  # Translatable is not migrated and the Drupal 8 default of true is used.
+  # If translatable is false in field storage then the field can not be
+  # set to translatable via the UI.
+  #translatable: translatable
+  cardinality: 'constants/cardinality'
+  settings: settings
+destination:
+  plugin: entity:field_storage_config
+migration_dependencies:
+  required:
+    - d7_file_entity_type
diff --git a/migrations/d7_media_source_field_config.yml b/migrations/d7_file_entity_source_field_config.yml
similarity index 76%
rename from migrations/d7_media_source_field_config.yml
rename to migrations/d7_file_entity_source_field_config.yml
index 5588f4e..0064e09 100644
--- a/migrations/d7_media_source_field_config.yml
+++ b/migrations/d7_file_entity_source_field_config.yml
@@ -1,22 +1,20 @@
-id: d7_media_source_field_config
+id: d7_file_entity_source_field_config
 label: Media source field instance configuration
 migration_tags:
   - Drupal 7
   - Configuration
-deriver: Drupal\media_migration\Plugin\migrate\D7FileEntityItemDeriver
+deriver: Drupal\media_migration\Plugin\migrate\D7FileEntityConfigDeriver
 source:
-  plugin: d7_media_source_field_instance
+  plugin: d7_file_entity_source_field_instance
   constants:
     entity_type_id: media
+    required: true
 process:
-  # If field name is empty, then the migration of the current row would throw an
-  # exception. Media Migration wants to avoid this.
-  field_name:
-    plugin: skip_on_empty
-    method: row
-    source: field_name
+  field_name: source_field_name
   entity_type: constants/entity_type_id
-  bundle: type
+  required: 'constants/required'
+  bundle: bundle
+  label: source_field_label
   # When the source plugin didn't find any additional file extensions which
   # should be allowed for this media field, then "file_extensions" will be
   # empty. But it also means that the preexisting configuration is appropriate,
@@ -47,3 +45,6 @@ process:
     source: title_field_required
 destination:
   plugin: entity:field_config
+migration_dependencies:
+  required:
+    - d7_file_entity_source_field
diff --git a/migrations/d7_file_entity_type.yml b/migrations/d7_file_entity_type.yml
index e669fc1..d973b07 100644
--- a/migrations/d7_file_entity_type.yml
+++ b/migrations/d7_file_entity_type.yml
@@ -3,20 +3,16 @@ label: File Entity to Media Bundle migration
 migration_tags:
   - Drupal 7
   - Configuration
-deriver: Drupal\media_migration\Plugin\migrate\D7FileEntityItemDeriver
+deriver: Drupal\media_migration\Plugin\migrate\D7FileEntityConfigDeriver
 source:
   plugin: d7_file_entity_type
   constants:
     status: true
 process:
-  id: type
-  label:
-    plugin: callback
-    source: type
-    callable: ucfirst
+  id: bundle
+  label: bundle_label
   status: constants/status
+  source: source_plugin_id
+  source_configuration/source_field: source_field_name
 destination:
   plugin: entity:media_type
-migration_dependencies:
-  required:
-    - d7_media_source_field_config
diff --git a/migrations/d7_file_entity_widget.yml b/migrations/d7_file_entity_widget.yml
new file mode 100644
index 0000000..612199e
--- /dev/null
+++ b/migrations/d7_file_entity_widget.yml
@@ -0,0 +1,22 @@
+id: d7_file_entity_widget
+label: File Entity media source field widget
+migration_tags:
+  - Drupal 7
+  - Configuration
+deriver: Drupal\media_migration\Plugin\migrate\D7FileEntityConfigDeriver
+source:
+  plugin: d7_file_entity_field_widget
+  constants:
+    entity_type_id: media
+    form_mode: default
+process:
+  entity_type: 'constants/entity_type_id'
+  bundle: bundle
+  form_mode: 'constants/form_mode'
+  field_name: source_field_name
+  options: options
+destination:
+  plugin: component_entity_form_display
+migration_dependencies:
+  required:
+    - d7_file_entity_source_field_config
diff --git a/src/Annotation/FileEntityDealer.php b/src/Annotation/FileEntityDealer.php
new file mode 100644
index 0000000..2b8abb9
--- /dev/null
+++ b/src/Annotation/FileEntityDealer.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\media_migration\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines FileEntityDealer annotation object.
+ *
+ * Plugin Namespace: Plugin\media_migration\file_entity.
+ *
+ * For a working example, see
+ * \Drupal\media_migration\Plugin\media_migration\file_entity\Image.
+ *
+ * @see \Drupal\media_migration\FileEntityDealerManager
+ * @see \Drupal\media_migration\FileEntityDealerPluginInterface
+ * @see \Drupal\media_migration\FileEntityDealerBase
+ * @see plugin_api
+ *
+ * @Annotation
+ */
+class FileEntityDealer extends Plugin {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The file entity types the plugin applies to.
+   *
+   * @var string[]
+   */
+  public $types;
+
+  /**
+   * The schemes the plugin applies to.
+   *
+   * Optional.
+   *
+   * @var string[]
+   */
+  public $schemes = [];
+
+  /**
+   * The ID of the destination media type's source plugin.
+   *
+   * Optional.
+   *
+   * @var string
+   */
+  public $destination_media_source_plugin_id = '';
+
+  /**
+   * The ID of the destination media type's base ID.
+   *
+   * Optional.
+   *
+   * @var string
+   */
+  public $destination_media_type_id_base = '';
+
+}
diff --git a/src/EventSubscriber/MediaMigrationSubscriber.php b/src/EventSubscriber/MediaMigrationSubscriber.php
index 099f686..d9eac13 100644
--- a/src/EventSubscriber/MediaMigrationSubscriber.php
+++ b/src/EventSubscriber/MediaMigrationSubscriber.php
@@ -72,22 +72,6 @@ class MediaMigrationSubscriber implements EventSubscriberInterface {
       }
     }
 
-    // Skip migrating field instances whose destination bundle does not exist.
-    if (in_array($source->getPluginId(), [
-      'd7_field_instance',
-      'd7_field_instance_per_view_mode',
-      'd7_field_instance_per_form_display',
-    ])) {
-      if ($row->getSourceProperty('entity_type') == 'file') {
-        // Don't migrate bundles which don't exist in the destination.
-        $media_bundle = $row->getSourceProperty('bundle');
-        if (!$this->entityTypeManager->getStorage('media_type')->load($media_bundle)) {
-          $field_name = $row->getSourceProperty('field_name');
-          throw new MigrateSkipRowException('Skipping field ' . $field_name . ' as its target is ' . $media_bundle . ', which does not exist in the destination.');
-        }
-      }
-    }
-
     // Transform entity reference fields pointing to file entities so
     // they point to media ones.
     if (($source->getPluginId() == 'd7_field') && ($row->getSourceProperty('type') == 'entityreference')) {
@@ -98,24 +82,6 @@ class MediaMigrationSubscriber implements EventSubscriberInterface {
       }
     }
 
-    // File types of type Document need to be mapped to the File media bundle
-    // when the target "document" bundle is missing but "file" exists. This is
-    // the case of every Drupal 8 target instance that was installed with core
-    // version lower than 8.8.x.
-    $migration_ids_to_be_mapped = [
-      'd7_file_entity_type',
-      'd7_file_entity_item',
-    ];
-    if (in_array($source->getPluginId(), $migration_ids_to_be_mapped, TRUE)) {
-      $document_type_is_missing = !$this->entityTypeManager->getStorage('media_type')->load('document');
-      $file_type_is_available = $this->entityTypeManager->getStorage('media_type')->load('file');
-      $source_type_is_document = $row->getSourceProperty('type') === 'document';
-
-      if ($source_type_is_document && $document_type_is_missing && $file_type_is_available) {
-        $row->setSourceProperty('type', 'file');
-      }
-    }
-
     // Map path alias sources from file/1234 to media/1234.
     if (($source->getPluginId() == 'd7_url_alias') && (strpos($row->getSourceProperty('source'), 'file/') === 0)) {
       $source_url = preg_replace('/^file/', 'media', $row->getSourceProperty('source'));
diff --git a/src/FileDealerBase.php b/src/FileDealerBase.php
index 7936359..b1ae5ee 100644
--- a/src/FileDealerBase.php
+++ b/src/FileDealerBase.php
@@ -2,151 +2,12 @@
 
 namespace Drupal\media_migration;
 
-use Drupal\Component\Plugin\Exception\PluginException;
-use Drupal\Component\Plugin\PluginManagerInterface;
 use Drupal\Core\Database\Connection;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Field\FieldTypePluginManagerInterface;
-use Drupal\Core\Plugin\PluginBase;
-use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\field\Entity\FieldConfig;
-use Drupal\field\Entity\FieldStorageConfig;
-use Drupal\field\FieldConfigInterface;
-use Drupal\field\FieldStorageConfigInterface;
-use Drupal\media\MediaSourceInterface;
-use Drupal\migrate\Row;
-use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Base implementation of file dealer plugins.
  */
-abstract class FileDealerBase extends PluginBase implements FileDealerPluginInterface, ContainerFactoryPluginInterface {
-
-  /**
-   * The media source plugin manager.
-   *
-   * @var \Drupal\Component\Plugin\PluginManagerInterface
-   */
-  protected $mediaSourceManager;
-
-  /**
-   * The field type plugin manager service.
-   *
-   * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
-   */
-  protected $fieldTypeManager;
-
-  /**
-   * The entity type manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
-   * Constructs a new plugin instance.
-   *
-   * @param array $configuration
-   *   A configuration array containing information about the plugin instance.
-   * @param string $plugin_id
-   *   The plugin_id for the plugin instance.
-   * @param mixed $plugin_definition
-   *   The plugin implementation definition.
-   * @param \Drupal\Component\Plugin\PluginManagerInterface $media_source_manager
-   *   Media source plugin manager.
-   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
-   *   The field type plugin manager service.
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   *   The entity type manager.
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, PluginManagerInterface $media_source_manager, FieldTypePluginManagerInterface $field_type_manager, EntityTypeManagerInterface $entity_type_manager) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-    $this->mediaSourceManager = $media_source_manager;
-    $this->fieldTypeManager = $field_type_manager;
-    $this->entityTypeManager = $entity_type_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
-    return new static(
-      $configuration,
-      $plugin_id,
-      $plugin_definition,
-      $container->get('plugin.manager.media.source'),
-      $container->get('plugin.manager.field.field_type'),
-      $container->get('entity_type.manager')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDestinationMediaTypeIdBase() {
-    return $this->pluginDefinition['destination_media_type_id_base'];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDestinationMediaTypeId() {
-    return implode('_', array_filter([
-      $this->getDestinationMediaTypeIdBase(),
-      $this->configuration['scheme'] === 'public' ? NULL : $this->configuration['scheme'],
-    ]));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDestinationMediaTypeLabel() {
-    return implode(' ', array_filter([
-      $this->getDestinationMediaTypeSourceFieldLabel(),
-      $this->configuration['scheme'] === 'public' ? NULL : $this->configuration['scheme'],
-    ]));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDestinationMediaTypeSourceFieldLabel() {
-    return ucfirst(preg_replace('/[\W|_]+/', ' ', strtolower($this->getDestinationMediaTypeIdBase())));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDestinationMediaSourceFieldName() {
-    return implode('_', array_filter([
-      'field',
-      'media',
-      str_replace(':', '_', $this->getDestinationMediaSourcePluginId()),
-      $this->configuration['scheme'] === 'public' ? NULL : $this->configuration['scheme'],
-    ]));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDestinationMediaSourcePluginId() {
-    return $this->pluginDefinition['destination_media_source_plugin_id'];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function alterMediaTypeMigrationDefinition(array &$migration_definition, Connection $connection): void {}
-
-  /**
-   * {@inheritdoc}
-   */
-  public function alterMediaSourceFieldStorageMigrationDefinition(array &$migration_definition, Connection $connection): void {}
-
-  /**
-   * {@inheritdoc}
-   */
-  public function alterMediaSourceFieldInstanceMigrationDefinition(array &$migration_definition, Connection $connection): void {}
+abstract class FileDealerBase extends MediaDealerBase implements FileDealerPluginInterface {
 
   /**
    * {@inheritdoc}
@@ -155,171 +16,4 @@ abstract class FileDealerBase extends PluginBase implements FileDealerPluginInte
     $migration_definition['process'][$this->getDestinationMediaSourceFieldName() . '/target_id'] = 'fid';
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function prepareMediaTypeRow(Row $row, Connection $connection): void {}
-
-  /**
-   * {@inheritdoc}
-   */
-  public function prepareMediaSourceFieldStorageRow(Row $row, Connection $connection): void {
-    $field_storage = $this->getMediaSourceFieldStorage();
-    $additional_properties = [
-      'field_type' => $field_storage->getType(),
-      'settings' => $field_storage->getSettings(),
-    ];
-    $additional_properties['settings']['uri_scheme'] = $row->getSourceProperty('scheme');
-
-    foreach ($additional_properties as $source_property => $source_value) {
-      $row->setSourceProperty($source_property, $source_value);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function prepareMediaSourceFieldInstanceRow(Row $row, Connection $connection): void {
-    $field_instance = $this->getMediaSourceFieldInstance();
-    $settings = $field_instance->getSettings();
-    $default_extensions = $settings['file_extensions'] ?? '';
-    $discovered_extensions = $row->getSourceProperty('file_extensions') ?? '';
-    $merged_file_extensions = implode(' ', array_filter(
-      array_unique(
-        array_merge(
-          explode(' ', $default_extensions),
-          explode(' ', $discovered_extensions)
-        )
-      )
-    ));
-    $settings['file_extensions'] = $merged_file_extensions;
-    $row->setSourceProperty('settings', $settings);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function prepareMediaEntityRow(Row $row, Connection $connection): void {}
-
-  /**
-   * Returns a media source field instance.
-   *
-   * The returned field instance ("field_config") entity that matches the media
-   * source plugin ID. When the destination media type does not exist, this is a
-   * new, unsaved media source field instance.
-   *
-   * The returned entity can be used for pre-populating the media type's source
-   * field's instance settings, e.g. for keeping every, previously allowed file
-   * extensions.
-   *
-   * @return \Drupal\field\FieldConfigInterface|null
-   *   A matching field instance config for the destination media type, or NULL
-   *   if it cannot be instantiated.
-   */
-  protected function getMediaSourceFieldInstance() {
-    $preexisting_field_instance = $this->entityTypeManager->getStorage('field_config')->load(implode('.', [
-      'media',
-      $this->getDestinationMediaTypeId(),
-      $this->getDestinationMediaSourceFieldName(),
-    ]));
-    if ($preexisting_field_instance) {
-      assert($preexisting_field_instance instanceof FieldConfigInterface);
-      return $preexisting_field_instance;
-    }
-
-    if (!($storage = $this->getMediaSourceFieldStorage())) {
-      return NULL;
-    }
-    $field_config = FieldConfig::create([
-      'field_storage' => $storage,
-      'bundle' => $this->getDestinationMediaTypeId(),
-      'label' => $this->getDestinationMediaTypeLabel(),
-      'required' => TRUE,
-    ]);
-
-    if (!($field_config instanceof FieldConfigInterface)) {
-      return NULL;
-    }
-
-    $default_settings = $this->fieldTypeManager->getDefaultFieldSettings($field_config->getType());
-    $extensions = explode(' ', $default_settings['file_extensions'] ?? '');
-    switch ($this->getDestinationMediaSourcePluginId()) {
-      case 'audio_file':
-        // Using the same defaults what the AudioFile source plugin defines.
-        // @see \Drupal\media\Plugin\media\Source\AudioFile::createSourceField()
-        $extensions = ['mp3', 'wav', 'aac'];
-        break;
-
-      case 'image':
-        // Using the same defaults what the Image source plugin defines.
-        // @see \Drupal\media\Plugin\media\Source\Image::createSourceField()
-        break;
-
-      case 'video_file':
-        // Using the same defaults what the VideoFile source plugin defines.
-        // @see \Drupal\media\Plugin\media\Source\VideoFile::createSourceField()
-        $extensions = ['mp4'];
-        break;
-
-      case 'file':
-        // Using the same defaults what the File source plugin defines.
-        // @see \Drupal\media\Plugin\media\Source\File::createSourceField()
-        $extensions = ['txt', 'doc', 'docx', 'pdf'];
-        break;
-    }
-
-    $default_settings['file_extensions'] = implode(' ', $extensions);
-    $field_config->set('settings', $default_settings);
-
-    return $field_config;
-  }
-
-  /**
-   * Returns a media source field storage.
-   *
-   * The returned field storage ("field_storage_config") entity that matches the
-   * media source plugin ID. When the destination media type does not exist,
-   * this is a new, unsaved media source field storage entity.
-   *
-   * The returned entity can be used for pre-populating the media type's source
-   * field's storage settings.
-   *
-   * @return \Drupal\field\FieldStorageConfigInterface|null
-   *   A matching field storage for the destination media type, or null if it
-   *   cannot be instantiated.
-   */
-  protected function getMediaSourceFieldStorage() {
-    $source_field_name = $this->getDestinationMediaSourceFieldName();
-    $preexisting_field_storage = $this->entityTypeManager->getStorage('field_storage_config')->load(implode('.', [
-      'media',
-      $source_field_name,
-    ]));
-    if ($preexisting_field_storage) {
-      assert($preexisting_field_storage instanceof FieldStorageConfigInterface);
-      return $preexisting_field_storage;
-    }
-
-    $source_plugin_id = $this->getDestinationMediaSourcePluginId();
-    try {
-      $media_source_plugin = $this->mediaSourceManager->createInstance($source_plugin_id);
-    }
-    catch (PluginException $e) {
-      // The specified plugin is invalid or missing.
-      return NULL;
-    }
-    assert($media_source_plugin instanceof MediaSourceInterface);
-    $source_plugin_definition = $media_source_plugin->getPluginDefinition();
-    $field_type = reset($source_plugin_definition['allowed_field_types']);
-    $field_storage = FieldStorageConfig::create([
-      'entity_type' => 'media',
-      'field_name' => $source_field_name,
-      'type' => $field_type,
-    ]);
-    assert($field_storage instanceof FieldStorageConfigInterface);
-
-    $field_storage->set('settings', $this->fieldTypeManager->getDefaultStorageSettings($field_storage->getType()));
-
-    return $field_storage;
-  }
-
 }
diff --git a/src/FileDealerManager.php b/src/FileDealerManager.php
index 0efe488..71af0c7 100644
--- a/src/FileDealerManager.php
+++ b/src/FileDealerManager.php
@@ -6,6 +6,7 @@ use Drupal\Component\Plugin\Exception\PluginException;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\media_migration\Annotation\FileDealer;
 
 /**
  * Manages discovery and instantiation of plain file dealer plugins.
@@ -37,8 +38,8 @@ class FileDealerManager extends DefaultPluginManager implements FileDealerManage
       'Plugin/media_migration/file',
       $namespaces,
       $module_handler,
-      'Drupal\media_migration\FileDealerPluginInterface',
-      'Drupal\media_migration\Annotation\FileDealer'
+      FileDealerPluginInterface::class,
+      FileDealer::class
     );
 
     $this->alterInfo('media_migration_file_dealer_info');
diff --git a/src/FileDealerPluginInterface.php b/src/FileDealerPluginInterface.php
index e6f46e7..0715394 100644
--- a/src/FileDealerPluginInterface.php
+++ b/src/FileDealerPluginInterface.php
@@ -2,142 +2,9 @@
 
 namespace Drupal\media_migration;
 
-use Drupal\Core\Database\Connection;
-use Drupal\migrate\Row;
-
 /**
  * Interface for plain file dealer plugins.
  */
-interface FileDealerPluginInterface {
-
-  /**
-   * Returns the destination media type's ID.
-   *
-   * @return string
-   *   The ID of the destination media type.
-   */
-  public function getDestinationMediaTypeId();
-
-  /**
-   * Returns the destination media type's ID base.
-   *
-   * @return string
-   *   The base ID of the destination media type.
-   */
-  public function getDestinationMediaTypeIdBase();
-
-  /**
-   * Returns the label of the destination media type.
-   *
-   * @return string
-   *   The label of the destination media type.
-   */
-  public function getDestinationMediaTypeLabel();
-
-  /**
-   * Returns the label of the destination media type's source field.
-   *
-   * @return string
-   *   The label of the source field.
-   */
-  public function getDestinationMediaTypeSourceFieldLabel();
-
-  /**
-   * Returns the destination media type's source field name.
-   *
-   * @return string
-   *   The name of the destination media type's source field.
-   */
-  public function getDestinationMediaSourceFieldName();
-
-  /**
-   * Returns the destination media type's source plugin ID.
-   *
-   * @return string
-   *   The ID of the destination media type's source plugin.
-   */
-  public function getDestinationMediaSourcePluginId();
-
-  /**
-   * Alters the definition of the media type migration.
-   *
-   * @param mixed[] $migration_definition
-   *   The migration definition of the current derived media type migration.
-   * @param \Drupal\Core\Database\Connection $connection
-   *   The database connection of the source Drupal 7 instance.
-   */
-  public function alterMediaTypeMigrationDefinition(array &$migration_definition, Connection $connection): void;
-
-  /**
-   * Alters the definition of the media source field storage migration.
-   *
-   * @param mixed[] $migration_definition
-   *   The migration definition of the current derived media source field
-   *   storage migration.
-   * @param \Drupal\Core\Database\Connection $connection
-   *   The database connection of the source Drupal 7 instance.
-   */
-  public function alterMediaSourceFieldStorageMigrationDefinition(array &$migration_definition, Connection $connection): void;
-
-  /**
-   * Alters the definition of the media source field instance migration.
-   *
-   * @param mixed[] $migration_definition
-   *   The migration definition of the current derived media source field
-   *   instance migration.
-   * @param \Drupal\Core\Database\Connection $connection
-   *   The database connection of the source Drupal 7 instance.
-   */
-  public function alterMediaSourceFieldInstanceMigrationDefinition(array &$migration_definition, Connection $connection): void;
-
-  /**
-   * Alters the definition of the media entity migration.
-   *
-   * @param mixed[] $migration_definition
-   *   The migration definition of the current derived media entity migration.
-   * @param \Drupal\Core\Database\Connection $connection
-   *   The database connection of the source Drupal 7 instance.
-   */
-  public function alterMediaEntityMigrationDefinition(array &$migration_definition, Connection $connection): void;
-
-  /**
-   * Prepares the migration row of the media type.
-   *
-   * @param \Drupal\migrate\Row $row
-   *   The current migration row.
-   * @param \Drupal\Core\Database\Connection $connection
-   *   The database connection of the source Drupal 7 instance.
-   */
-  public function prepareMediaTypeRow(Row $row, Connection $connection): void;
-
-  /**
-   * Prepares the migration row of media source field storages.
-   *
-   * @param \Drupal\migrate\Row $row
-   *   The current migration row.
-   * @param \Drupal\Core\Database\Connection $connection
-   *   The database connection of the source Drupal 7 instance.
-   */
-  public function prepareMediaSourceFieldStorageRow(Row $row, Connection $connection): void;
-
-  /**
-   * Prepares the migration row of media source field instances.
-   *
-   * @param \Drupal\migrate\Row $row
-   *   The current migration row.
-   * @param \Drupal\Core\Database\Connection $connection
-   *   The database connection of the source Drupal 7 instance.
-   */
-  public function prepareMediaSourceFieldInstanceRow(Row $row, Connection $connection): void;
-
-  /**
-   * Prepares the migration row of media items.
-   *
-   * @param \Drupal\migrate\Row $row
-   *   The current migration row.
-   * @param \Drupal\Core\Database\Connection $connection
-   *   The database connection of the source Drupal 7 instance.
-   */
-  public function prepareMediaEntityRow(Row $row, Connection $connection): void;
+interface FileDealerPluginInterface extends MediaDealerPluginInterface {
 
 }
diff --git a/src/FileEntityDealerBase.php b/src/FileEntityDealerBase.php
new file mode 100644
index 0000000..fef4a60
--- /dev/null
+++ b/src/FileEntityDealerBase.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Drupal\media_migration;
+
+/**
+ * Base implementation of a file entity dealer plugin.
+ */
+abstract class FileEntityDealerBase extends MediaDealerBase implements FileEntityDealerPluginInterface {
+
+}
diff --git a/src/FileEntityDealerManager.php b/src/FileEntityDealerManager.php
new file mode 100644
index 0000000..f5be3d5
--- /dev/null
+++ b/src/FileEntityDealerManager.php
@@ -0,0 +1,109 @@
+<?php
+
+namespace Drupal\media_migration;
+
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\media_migration\Annotation\FileEntityDealer;
+
+/**
+ * Manages discovery and instantiation of file entity dealer plugins.
+ *
+ * @see \Drupal\media_migration\FileEntityDealerPluginInterface
+ */
+class FileEntityDealerManager extends DefaultPluginManager implements FileEntityDealerManagerInterface {
+
+  /**
+   * The logger.
+   *
+   * @var \Psr\Log\LoggerInterface
+   */
+  protected $logger;
+
+  /**
+   * Constructs a FileEntityDealerManager instance.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    parent::__construct(
+      'Plugin/media_migration/file_entity',
+      $namespaces,
+      $module_handler,
+      FileEntityDealerPluginInterface::class,
+      FileEntityDealer::class
+    );
+
+    $this->alterInfo('media_migration_file_entity_dealer_info');
+    $this->setCacheBackend($cache_backend, 'file_entity_dealer_plugins');
+  }
+
+  /**
+   * Gets the plugin definitions for the specified file entity type.
+   *
+   * @param string $type
+   *   The file entity type.
+   * @param string $scheme
+   *   The URI scheme.
+   *
+   * @return mixed[]
+   *   An array of the matching plugin definitions (empty array if no
+   *   definitions were found).
+   */
+  protected function getDefinitionsByTypeAndScheme(string $type, string $scheme) {
+    $definitions = $this->getDefinitions();
+
+    $strict_list = array_filter($this->getDefinitions(), function ($definition) use ($type, $scheme) {
+      return in_array($type, $definition['types'], TRUE) && in_array($scheme, $definition['schemes'], TRUE);
+    });
+    if (!empty($strict_list)) {
+      return $strict_list;
+    }
+
+    $only_type_list = array_filter($definitions, function ($definition) use ($type) {
+      return in_array($type, $definition['types'], TRUE) && empty($definition['schemes']);
+    });
+    if (!empty($only_type_list)) {
+      return $only_type_list;
+    }
+
+    $only_scheme_list = array_filter($definitions, function ($definition) use ($scheme) {
+      return empty($definition['types']) && in_array($scheme, $definition['schemes'], TRUE);
+    });
+    if (!empty($only_scheme_list)) {
+      return $only_scheme_list;
+    }
+
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createInstanceFromTypeAndScheme(string $type, string $scheme) {
+    $filtered_definitions = $this->getDefinitionsByTypeAndScheme($type, $scheme);
+    if (!empty($filtered_definitions)) {
+      try {
+        $configuration = [
+          'type' => $type,
+          'scheme' => $scheme,
+        ];
+        $plugin_id = array_keys($filtered_definitions)[0];
+        return $this->createInstance($plugin_id, $configuration);
+      }
+      catch (PluginException $e) {
+        return NULL;
+      }
+    }
+    return NULL;
+  }
+
+}
diff --git a/src/FileEntityDealerManagerInterface.php b/src/FileEntityDealerManagerInterface.php
new file mode 100644
index 0000000..6c78ff3
--- /dev/null
+++ b/src/FileEntityDealerManagerInterface.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\media_migration;
+
+/**
+ * Interface of FileEntityDealerManager.
+ *
+ * @see \Drupal\media_migration\FileEntityDealerManager
+ */
+interface FileEntityDealerManagerInterface {
+
+  /**
+   * Gets the plugin definitions for the specified file entity type.
+   *
+   * @param string $type
+   *   The file entity type.
+   * @param string $scheme
+   *   The URI scheme.
+   *
+   * @return \Drupal\media_migration\FileEntityDealerPluginInterface|null
+   *   A fully configured plugin instance or NULL if no applicable plugin was
+   *   found.
+   */
+  public function createInstanceFromTypeAndScheme(string $type, string $scheme);
+
+}
diff --git a/src/FileEntityDealerPluginInterface.php b/src/FileEntityDealerPluginInterface.php
new file mode 100644
index 0000000..186d480
--- /dev/null
+++ b/src/FileEntityDealerPluginInterface.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Drupal\media_migration;
+
+/**
+ * Interface for file entity dealer plugins.
+ */
+interface FileEntityDealerPluginInterface extends MediaDealerPluginInterface {
+
+}
diff --git a/src/MediaDealerBase.php b/src/MediaDealerBase.php
new file mode 100644
index 0000000..ed763ac
--- /dev/null
+++ b/src/MediaDealerBase.php
@@ -0,0 +1,427 @@
+<?php
+
+namespace Drupal\media_migration;
+
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\field\FieldConfigInterface;
+use Drupal\field\FieldStorageConfigInterface;
+use Drupal\media\MediaSourceInterface;
+use Drupal\migrate\Row;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Base implementation of media dealer plugins.
+ */
+abstract class MediaDealerBase extends PluginBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The media source plugin manager.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $mediaSourceManager;
+
+  /**
+   * The field type plugin manager service.
+   *
+   * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
+   */
+  protected $fieldTypeManager;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a new plugin instance.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Component\Plugin\PluginManagerInterface $media_source_manager
+   *   Media source plugin manager.
+   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
+   *   The field type plugin manager service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, PluginManagerInterface $media_source_manager, FieldTypePluginManagerInterface $field_type_manager, EntityTypeManagerInterface $entity_type_manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->mediaSourceManager = $media_source_manager;
+    $this->fieldTypeManager = $field_type_manager;
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('plugin.manager.media.source'),
+      $container->get('plugin.manager.field.field_type'),
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaTypeIdBase() {
+    return $this->pluginDefinition['destination_media_type_id_base'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaTypeId() {
+    return implode('_', array_filter([
+      $this->getDestinationMediaTypeIdBase(),
+      $this->configuration['scheme'] === 'public' ? NULL : $this->configuration['scheme'],
+    ]));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaTypeLabel() {
+    return implode(' ', array_filter([
+      $this->getDestinationMediaTypeSourceFieldLabel(),
+      $this->configuration['scheme'] === 'public' ? NULL : $this->configuration['scheme'],
+    ]));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaTypeSourceFieldLabel() {
+    return ucfirst(preg_replace('/[\W|_]+/', ' ', strtolower($this->getDestinationMediaTypeIdBase())));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaSourceFieldName() {
+    return implode('_', array_filter([
+      'field',
+      'media',
+      str_replace(':', '_', $this->getDestinationMediaSourcePluginId()),
+      $this->configuration['scheme'] === 'public' ? NULL : $this->configuration['scheme'],
+    ]));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaSourcePluginId() {
+    return $this->pluginDefinition['destination_media_source_plugin_id'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterMediaTypeMigrationDefinition(array &$migration_definition, Connection $connection): void {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterMediaSourceFieldStorageMigrationDefinition(array &$migration_definition, Connection $connection): void {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterMediaSourceFieldInstanceMigrationDefinition(array &$migration_definition, Connection $connection): void {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterMediaSourceFieldWidgetMigrationDefinition(array &$migration_definition, Connection $connection): void {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterMediaFieldFormatterMigrationDefinition(array &$migration_definition, Connection $connection): void {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterMediaEntityMigrationDefinition(array &$migration_definition, Connection $connection): void {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaSourceFieldStorageRow(Row $row, Connection $connection): void {
+    $dummy_field_storage = $this->getMediaSourceFieldStorage();
+    $additional_properties = [
+      'field_type' => $dummy_field_storage->getType(),
+      'settings' => $dummy_field_storage->getSettings(),
+    ];
+    $additional_properties['settings']['uri_scheme'] = $row->getSourceProperty('scheme');
+
+    foreach ($additional_properties as $source_property => $source_value) {
+      $row->setSourceProperty($source_property, $source_value);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaSourceFieldInstanceRow(Row $row, Connection $connection): void {
+    $field_instance_default = $this->getMediaSourceFieldInstance();
+    $settings = $field_instance_default->getSettings();
+    $default_extensions = $settings['file_extensions'] ?? '';
+    $discovered_extensions = $row->getSourceProperty('file_extensions') ?? '';
+    $merged_file_extensions = implode(' ', array_filter(array_unique(array_merge(explode(' ', $default_extensions), explode(' ', $discovered_extensions)))));
+    $settings['file_extensions'] = $merged_file_extensions;
+    $row->setSourceProperty('settings', $settings);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaSourceFieldWidgetRow(Row $row, Connection $connection): void {
+    $source_field_definition = $this->fieldTypeManager->getDefinition($this->getMediaSourceFieldStorage()->getType(), FALSE) ?? [];
+    $default_widget = $source_field_definition['default_widget'] ?? NULL;
+
+    if ($default_widget) {
+      $row->setSourceProperty('options', [
+        'type' => $default_widget,
+        'weight' => 0,
+      ]);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaSourceFieldFormatterRow(Row $row, Connection $connection): void {
+    $source_field_definition = $this->fieldTypeManager->getDefinition($this->getMediaSourceFieldStorage()->getType(), FALSE) ?? [];
+    $default_formatter = $source_field_definition['default_formatter'] ?? NULL;
+
+    if ($default_formatter) {
+      $row->setSourceProperty('options', [
+        'type' => $default_formatter,
+        'weight' => 0,
+        'label' => 'visually_hidden',
+      ]);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaTypeRow(Row $row, Connection $connection): void {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaEntityRow(Row $row, Connection $connection): void {}
+
+  /**
+   * Returns a media source field instance.
+   *
+   * The returned field instance ("field_config") entity that matches the media
+   * source plugin ID. When the destination media type does not exist, this is a
+   * new, unsaved media source field instance.
+   *
+   * The returned entity can be used for pre-populating the media type's source
+   * field's instance settings, e.g. for keeping every, previously allowed file
+   * extensions.
+   *
+   * @return \Drupal\field\FieldConfigInterface|null
+   *   A matching field instance config for the destination media type, or NULL
+   *   if it cannot be instantiated.
+   */
+  protected function getMediaSourceFieldInstance() {
+    $preexisting_field_instance = $this->entityTypeManager->getStorage('field_config')->load(implode('.', [
+      'media',
+      $this->getDestinationMediaTypeId(),
+      $this->getDestinationMediaSourceFieldName(),
+    ]));
+    if ($preexisting_field_instance) {
+      assert($preexisting_field_instance instanceof FieldConfigInterface);
+      return $preexisting_field_instance;
+    }
+
+    if (!($storage = $this->getMediaSourceFieldStorage())) {
+      return NULL;
+    }
+    $field_config = FieldConfig::create([
+      'field_storage' => $storage,
+      'bundle' => $this->getDestinationMediaTypeId(),
+      'label' => $this->getDestinationMediaTypeLabel(),
+      'required' => TRUE,
+    ]);
+
+    if (!($field_config instanceof FieldConfigInterface)) {
+      return NULL;
+    }
+
+    $default_settings = $this->fieldTypeManager->getDefaultFieldSettings($field_config->getType());
+    $extensions = explode(' ', $default_settings['file_extensions'] ?? '');
+    switch ($this->getDestinationMediaSourcePluginId()) {
+      case 'audio_file':
+        // Using the same defaults what the AudioFile source plugin defines.
+        // @see \Drupal\media\Plugin\media\Source\AudioFile::createSourceField()
+        $extensions = ['mp3', 'wav', 'aac'];
+        break;
+
+      case 'image':
+        // Using the same defaults what the Image source plugin defines.
+        // @see \Drupal\media\Plugin\media\Source\Image::createSourceField()
+        break;
+
+      case 'video_file':
+        // Using the same defaults what the VideoFile source plugin defines.
+        // @see \Drupal\media\Plugin\media\Source\VideoFile::createSourceField()
+        $extensions = ['mp4'];
+        break;
+
+      case 'file':
+        // Using the same defaults what the File source plugin defines.
+        // @see \Drupal\media\Plugin\media\Source\File::createSourceField()
+        $extensions = ['txt', 'doc', 'docx', 'pdf'];
+        break;
+    }
+
+    $default_settings['file_extensions'] = implode(' ', $extensions);
+    $field_config->set('settings', $default_settings);
+
+    return $field_config;
+  }
+
+  /**
+   * Returns a media source field storage.
+   *
+   * The returned field storage ("field_storage_config") entity that matches the
+   * media source plugin ID. When the destination media type does not exist,
+   * this is a new, unsaved media source field storage entity.
+   *
+   * The returned entity can be used for pre-populating the media type's source
+   * field's storage settings.
+   *
+   * @return \Drupal\field\FieldStorageConfigInterface|null
+   *   A matching field storage for the destination media type, or NULL if it
+   *   cannot be instantiated.
+   */
+  protected function getMediaSourceFieldStorage() {
+    $source_field_name = $this->getDestinationMediaSourceFieldName();
+    $preexisting_field_storage = $this->entityTypeManager->getStorage('field_storage_config')->load(implode('.', [
+      'media',
+      $source_field_name,
+    ]));
+    if ($preexisting_field_storage) {
+      assert($preexisting_field_storage instanceof FieldStorageConfigInterface);
+      return $preexisting_field_storage;
+    }
+
+    $source_plugin_id = $this->getDestinationMediaSourcePluginId();
+    try {
+      $media_source_plugin = $this->mediaSourceManager->createInstance($source_plugin_id);
+    }
+    catch (PluginException $e) {
+      // The specified plugin is invalid or missing.
+      return NULL;
+    }
+    assert($media_source_plugin instanceof MediaSourceInterface);
+    $source_plugin_definition = $media_source_plugin->getPluginDefinition();
+    $field_type = reset($source_plugin_definition['allowed_field_types']);
+    $field_storage = FieldStorageConfig::create([
+      'entity_type' => 'media',
+      'field_name' => $source_field_name,
+      'type' => $field_type,
+    ]);
+    assert($field_storage instanceof FieldStorageConfigInterface);
+
+    $field_storage->set('settings', $this->fieldTypeManager->getDefaultStorageSettings($field_storage->getType()));
+
+    return $field_storage;
+  }
+
+  /**
+   * Get the names of the image type fields from the source database.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   * @param bool $field_names_only
+   *   Whether only the name of the image fields should be returned. Defaults to
+   *   TRUE.
+   *
+   * @return array
+   *   The array of the available image fields.
+   */
+  protected function getImageFieldData(Connection $connection, $field_names_only = TRUE): array {
+    $image_field_query = $connection->select('field_config', 'fs')
+      ->fields('fs', ['field_name'])
+      ->condition('fs.type', 'image')
+      ->condition('fs.active', 1)
+      ->condition('fs.deleted', 0)
+      ->condition('fs.storage_active', 1)
+      ->condition('fi.deleted', 0);
+    $image_field_query->join('field_config_instance', 'fi', 'fs.id = fi.field_id');
+
+    if ($field_names_only) {
+      return array_keys($image_field_query->execute()->fetchAllAssoc('field_name'));
+    }
+
+    $image_field_query->addField('fs', 'data', 'field_storage_data');
+    $image_field_query->addField('fi', 'data', 'field_instance_data');
+
+    $image_fields_data = [];
+    foreach ($image_field_query->execute()->fetchAll(\PDO::FETCH_ASSOC) as $item) {
+      foreach (['field_storage_data', 'field_instance_data'] as $data_key) {
+        $item[$data_key] = unserialize($item[$data_key]);
+      }
+      $image_fields_data[] = $item;
+    }
+
+    return $image_fields_data;
+  }
+
+  /**
+   * Returns alt, title, with and height properties of the specified file.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   * @param string|int $file_id
+   *   The ID of the file.
+   *
+   * @return array
+   *   An array of those properties whose value is not empty.
+   */
+  protected function getImageData(Connection $connection, $file_id): array {
+    foreach ($this->getImageFieldData($connection) as $field_name) {
+      $field_table_name = "field_data_$field_name";
+      $data_query = $connection->select($field_table_name, $field_name);
+      $data_query->addField($field_name, "{$field_name}_alt", 'alt');
+      $data_query->addField($field_name, "{$field_name}_title", 'title');
+      $data_query->addField($field_name, "{$field_name}_height", 'height');
+      $data_query->addField($field_name, "{$field_name}_width", 'width');
+      $data_query->condition("{$field_name}_fid", $file_id);
+
+      if (!empty($results = $data_query->execute()->fetchAll(\PDO::FETCH_ASSOC))) {
+        $result = reset($results);
+        return array_filter($result);
+      }
+    }
+
+    return [];
+  }
+
+}
diff --git a/src/MediaDealerPluginInterface.php b/src/MediaDealerPluginInterface.php
new file mode 100644
index 0000000..dd2cfc0
--- /dev/null
+++ b/src/MediaDealerPluginInterface.php
@@ -0,0 +1,185 @@
+<?php
+
+namespace Drupal\media_migration;
+
+use Drupal\Core\Database\Connection;
+use Drupal\migrate\Row;
+
+/**
+ * Base interface for media dealer plugins.
+ */
+interface MediaDealerPluginInterface {
+
+  /**
+   * Returns the destination media type's ID.
+   *
+   * @return string
+   *   The ID of the destination media type.
+   */
+  public function getDestinationMediaTypeId();
+
+  /**
+   * Returns the destination media type's ID base.
+   *
+   * @return string
+   *   The base ID of the destination media type.
+   */
+  public function getDestinationMediaTypeIdBase();
+
+  /**
+   * Returns the label of the destination media type.
+   *
+   * @return string
+   *   The label of the destination media type.
+   */
+  public function getDestinationMediaTypeLabel();
+
+  /**
+   * Returns the label of the destination media type's source field.
+   *
+   * @return string
+   *   The label of the source field.
+   */
+  public function getDestinationMediaTypeSourceFieldLabel();
+
+  /**
+   * Returns the destination media type's source field name.
+   *
+   * @return string
+   *   The name of the destination media type's source field.
+   */
+  public function getDestinationMediaSourceFieldName();
+
+  /**
+   * Returns the destination media type's source plugin ID.
+   *
+   * @return string
+   *   The ID of the destination media type's source plugin.
+   */
+  public function getDestinationMediaSourcePluginId();
+
+  /**
+   * Alters the definition of the media type migration.
+   *
+   * @param mixed[] $migration_definition
+   *   The migration definition of the current derived media type migration.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   */
+  public function alterMediaTypeMigrationDefinition(array &$migration_definition, Connection $connection): void;
+
+  /**
+   * Alters the definition of the media source field storage migration.
+   *
+   * @param mixed[] $migration_definition
+   *   The migration definition of the current derived media source field
+   *   storage migration.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   */
+  public function alterMediaSourceFieldStorageMigrationDefinition(array &$migration_definition, Connection $connection): void;
+
+  /**
+   * Alters the definition of the media source field instance migration.
+   *
+   * @param mixed[] $migration_definition
+   *   The migration definition of the current derived media source field
+   *   instance migration.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   */
+  public function alterMediaSourceFieldInstanceMigrationDefinition(array &$migration_definition, Connection $connection): void;
+
+  /**
+   * Alters the definition of the media source field widget settings migration.
+   *
+   * @param mixed[] $migration_definition
+   *   The migration definition of the current derived media source field
+   *   widget settings migration.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   */
+  public function alterMediaSourceFieldWidgetMigrationDefinition(array &$migration_definition, Connection $connection): void;
+
+  /**
+   * Alters the definition of the media field's formatter settings migration.
+   *
+   * @param mixed[] $migration_definition
+   *   The migration definition of the current derived media field's formatter
+   *   settings migration.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   */
+  public function alterMediaFieldFormatterMigrationDefinition(array &$migration_definition, Connection $connection): void;
+
+  /**
+   * Alters the definition of the media entity migration.
+   *
+   * @param mixed[] $migration_definition
+   *   The migration definition of the current derived media entity migration.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   */
+  public function alterMediaEntityMigrationDefinition(array &$migration_definition, Connection $connection): void;
+
+  /**
+   * Prepares the migration row of a media type.
+   *
+   * @param \Drupal\migrate\Row $row
+   *   The current migration row.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   */
+  public function prepareMediaTypeRow(Row $row, Connection $connection): void;
+
+  /**
+   * Prepares the migration row of a media source field storage.
+   *
+   * @param \Drupal\migrate\Row $row
+   *   The current migration row.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   */
+  public function prepareMediaSourceFieldStorageRow(Row $row, Connection $connection): void;
+
+  /**
+   * Prepares the migration row of a media source field instance.
+   *
+   * @param \Drupal\migrate\Row $row
+   *   The current migration row.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   */
+  public function prepareMediaSourceFieldInstanceRow(Row $row, Connection $connection): void;
+
+  /**
+   * Prepares the migration row of a media field widget configuration.
+   *
+   * @param \Drupal\migrate\Row $row
+   *   The current migration row.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   */
+  public function prepareMediaSourceFieldWidgetRow(Row $row, Connection $connection): void;
+
+  /**
+   * Prepares the migration row of a media field formatter configuration.
+   *
+   * @param \Drupal\migrate\Row $row
+   *   The current migration row.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   */
+  public function prepareMediaSourceFieldFormatterRow(Row $row, Connection $connection): void;
+
+  /**
+   * Prepares the migration row of a media item.
+   *
+   * @param \Drupal\migrate\Row $row
+   *   The current migration row.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   */
+  public function prepareMediaEntityRow(Row $row, Connection $connection): void;
+
+}
diff --git a/src/MigratePluginAlterer.php b/src/MigratePluginAlterer.php
index fc55813..d304a0b 100644
--- a/src/MigratePluginAlterer.php
+++ b/src/MigratePluginAlterer.php
@@ -111,6 +111,30 @@ class MigratePluginAlterer {
    *   If a plugin cannot be found.
    */
   protected function alterFieldMigrations(array &$migrations) {
+    // Collect all file entity -> media entity migration which have additional
+    // fields. Although these migrations depend on the "d7_field_instance"
+    // migration (and implicitly on "d7_field" migration), it isn't enough:
+    // the parent media type entity has to exist prior its all field storage
+    // entity being created, so "d7_field" (and maybe "d7_field_instance") has
+    // to depend on the media type migrations which have additional fields.
+    $media_types_with_fields_migration_ids = array_reduce($migrations, function (array $carry, array $migration_def) {
+      $deps_required = isset($migration_def['migration_dependencies']['required'])
+        ? $migration_def['migration_dependencies']['required']
+        : [];
+
+      if (!in_array('d7_field_instance', $deps_required, TRUE)) {
+        return $carry;
+      }
+
+      foreach ($deps_required as $requirement) {
+        $requirement_parts = explode(':', $requirement);
+        if ($requirement_parts[0] === 'd7_file_entity_type') {
+          $carry[$requirement] = $requirement;
+        }
+      }
+      return $carry;
+    }, []);
+
     foreach ($migrations as &$migration) {
       $migration_stub = $this->pluginManagerMigration->createStubMigration($migration);
       $source = NULL;
@@ -129,6 +153,28 @@ class MigratePluginAlterer {
         if (is_a($source, ViewMode::class)) {
           $this->mapFileToMediaBundle($migration);
         }
+
+        // D7 field storage and field instance migrations should depend on
+        // media types which have extra fields, because field storage config
+        // entities require their host entity.
+        $field_storage_and_instance_source_plugin_ids = [
+          'd7_field',
+          'd7_field_instance',
+        ];
+        if (
+          in_array($migration['source']['plugin'], $field_storage_and_instance_source_plugin_ids) &&
+          !empty($media_types_with_fields_migration_ids)
+        ) {
+          $required_migration_deps = isset($migration['migration_dependencies']['required'])
+            ? $migration['migration_dependencies']['required']
+            : [];
+          $migration['migration_dependencies']['required'] = array_unique(
+            array_merge(
+              $required_migration_deps,
+              array_values($media_types_with_fields_migration_ids)
+            )
+          );
+        }
       }
     }
   }
diff --git a/src/Plugin/media_migration/file/Fallback.php b/src/Plugin/media_migration/file/Fallback.php
index 7faf541..aa49f79 100644
--- a/src/Plugin/media_migration/file/Fallback.php
+++ b/src/Plugin/media_migration/file/Fallback.php
@@ -65,6 +65,21 @@ class Fallback extends FileDealerBase {
     return parent::getDestinationMediaSourceFieldName();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaTypeSourceFieldLabel() {
+    switch ($this->getDestinationMediaSourcePluginId()) {
+      case 'audio_file':
+        return 'Audio file';
+
+      case 'video_file':
+        return 'Video file';
+    }
+
+    return parent::getDestinationMediaTypeSourceFieldLabel();
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/src/Plugin/media_migration/file/Image.php b/src/Plugin/media_migration/file/Image.php
index 7d57997..ba95bff 100644
--- a/src/Plugin/media_migration/file/Image.php
+++ b/src/Plugin/media_migration/file/Image.php
@@ -46,74 +46,4 @@ class Image extends FileDealerBase {
     }
   }
 
-  /**
-   * Get the names of the image type fields from the source database.
-   *
-   * @param \Drupal\Core\Database\Connection $connection
-   *   The database connection of the source Drupal 7 instance.
-   * @param bool $field_names_only
-   *   Whether only the name of the image fields should be returned. Defaults to
-   *   TRUE.
-   *
-   * @return array
-   *   The array of the available image fields.
-   */
-  protected function getImageFieldData(Connection $connection, $field_names_only = TRUE): array {
-    $image_field_query = $connection->select('field_config', 'fs')
-      ->fields('fs', ['field_name'])
-      ->condition('fs.type', 'image')
-      ->condition('fs.active', 1)
-      ->condition('fs.deleted', 0)
-      ->condition('fs.storage_active', 1)
-      ->condition('fi.deleted', 0);
-    $image_field_query->join('field_config_instance', 'fi', 'fs.id = fi.field_id');
-
-    if ($field_names_only) {
-      return array_keys($image_field_query->execute()->fetchAllAssoc('field_name'));
-    }
-
-    $image_field_query->addField('fs', 'data', 'field_storage_data');
-    $image_field_query->addField('fi', 'data', 'field_instance_data');
-
-    $image_fields_data = [];
-    foreach ($image_field_query->execute()->fetchAll(\PDO::FETCH_ASSOC) as $item) {
-      foreach (['field_storage_data', 'field_instance_data'] as $data_key) {
-        $item[$data_key] = unserialize($item[$data_key]);
-      }
-      $image_fields_data[] = $item;
-    }
-
-    return $image_fields_data;
-  }
-
-  /**
-   * Returns alt, title, with and height properties of the specified file.
-   *
-   * @param \Drupal\Core\Database\Connection $connection
-   *   The database connection of the source Drupal 7 instance.
-   * @param string|int $file_id
-   *   The ID of the file.
-   *
-   * @return array
-   *   An array of those properties whose value is not empty.
-   */
-  protected function getImageData(Connection $connection, $file_id): array {
-    foreach ($this->getImageFieldData($connection) as $field_name) {
-      $field_table_name = "field_data_$field_name";
-      $data_query = $connection->select($field_table_name, $field_name);
-      $data_query->addField($field_name, "{$field_name}_alt", 'alt');
-      $data_query->addField($field_name, "{$field_name}_title", 'title');
-      $data_query->addField($field_name, "{$field_name}_height", 'height');
-      $data_query->addField($field_name, "{$field_name}_width", 'width');
-      $data_query->condition("{$field_name}_fid", $file_id);
-
-      if (!empty($results = $data_query->execute()->fetchAll(\PDO::FETCH_ASSOC))) {
-        $result = reset($results);
-        return array_filter($result);
-      }
-    }
-
-    return [];
-  }
-
 }
diff --git a/src/Plugin/media_migration/file_entity/Audio.php b/src/Plugin/media_migration/file_entity/Audio.php
new file mode 100644
index 0000000..eb884c4
--- /dev/null
+++ b/src/Plugin/media_migration/file_entity/Audio.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\media_migration\file_entity;
+
+use Drupal\Core\Database\Connection;
+use Drupal\migrate\Row;
+
+/**
+ * Audio media migration plugin for local audio media.
+ *
+ * @FileEntityDealer(
+ *   id = "audio",
+ *   types = {"audio"},
+ *   destination_media_type_id_base = "audio",
+ *   destination_media_source_plugin_id = "audio_file"
+ * )
+ */
+class Audio extends FileBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaTypeSourceFieldLabel() {
+    return 'Audio file';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaTypeLabel() {
+    return implode(' ', array_filter([
+      'Audio',
+      $this->configuration['scheme'] === 'public' ? NULL : "({$this->configuration['scheme']})",
+    ]));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaSourceFieldFormatterRow(Row $row, Connection $connection): void {
+    parent::prepareMediaSourceFieldFormatterRow($row, $connection);
+    $options = [
+      'type' => 'file_audio',
+      'settings' => [
+        'controls' => TRUE,
+        'autoplay' => FALSE,
+        'loop' => FALSE,
+        'multiple_file_display_type' => 'tags',
+      ],
+    ] + $row->getSourceProperty('options') ?? [];
+    $row->setSourceProperty('options', $options);
+  }
+
+}
diff --git a/src/Plugin/media_migration/file_entity/Document.php b/src/Plugin/media_migration/file_entity/Document.php
new file mode 100644
index 0000000..6c0bf1f
--- /dev/null
+++ b/src/Plugin/media_migration/file_entity/Document.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\media_migration\file_entity;
+
+/**
+ * Document media migration plugin for local document media entities.
+ *
+ * @FileEntityDealer(
+ *   id = "document",
+ *   types = {"document"},
+ *   destination_media_type_id_base = "document",
+ *   destination_media_source_plugin_id = "file"
+ * )
+ */
+class Document extends FileBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaSourceFieldName() {
+    return 'field_media_document';
+  }
+
+}
diff --git a/src/Plugin/media_migration/file_entity/FileBase.php b/src/Plugin/media_migration/file_entity/FileBase.php
new file mode 100644
index 0000000..50f341f
--- /dev/null
+++ b/src/Plugin/media_migration/file_entity/FileBase.php
@@ -0,0 +1,109 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\media_migration\file_entity;
+
+use Drupal\Core\Database\Connection;
+use Drupal\media_migration\FileEntityDealerBase;
+use Drupal\migrate\Row;
+
+/**
+ * Abstract plugin class for file-based media migration source plugins.
+ *
+ * Not every file entity will be migrated into a media file field: for example,
+ * remote ("YouTube" or "Vimeo") file entities are migrated into a media source
+ * field with type "string". This is a base plugin class for those file entity
+ * dealer plugins which needs to migrate file entities to media entities with
+ * file-based source field (linke "file" or "image").
+ */
+abstract class FileBase extends FileEntityDealerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterMediaEntityMigrationDefinition(array &$migration_definition, Connection $connection): void {
+    $source_field_name = $this->getDestinationMediaSourceFieldName();
+    $migration_definition['process'][$source_field_name . '/target_id'] = 'fid';
+    $migration_definition['process'][$source_field_name . '/display'] = 'display';
+    $migration_definition['process'][$source_field_name . '/description'] = 'description';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaEntityRow(Row $row, Connection $connection): void {
+    parent::prepareMediaEntityRow($row, $connection);
+
+    foreach ($this->getFileData($connection, $row->getSourceProperty('fid')) as $data_key => $data_value) {
+      $row->setSourceProperty($data_key, $data_value);
+    }
+  }
+
+  /**
+   * Get the name of the file fields from the source database.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   * @param bool $field_names_only
+   *   Whether only the name of the file fields should be returned. Defaults to
+   *   TRUE.
+   *
+   * @return array
+   *   The array of the available file fields.
+   */
+  protected function getFileFieldData(Connection $connection, bool $field_names_only = TRUE): array {
+    $field_query = $connection->select('field_config', 'fs')
+      ->fields('fs', ['field_name'])
+      ->condition('fs.type', 'file')
+      ->condition('fs.active', 1)
+      ->condition('fs.deleted', 0)
+      ->condition('fs.storage_active', 1)
+      ->condition('fi.deleted', 0);
+    $field_query->join('field_config_instance', 'fi', 'fs.id = fi.field_id');
+
+    if ($field_names_only) {
+      return array_keys($field_query->execute()->fetchAllAssoc('field_name'));
+    }
+
+    $field_query->addField('fs', 'data', 'field_storage_data');
+    $field_query->addField('fi', 'data', 'field_instance_data');
+
+    $fields_data = [];
+    foreach ($field_query->execute()->fetchAll(\PDO::FETCH_ASSOC) as $item) {
+      foreach (['field_storage_data', 'field_instance_data'] as $data_key) {
+        $item[$data_key] = unserialize($item[$data_key]);
+      }
+      $fields_data[] = $item;
+    }
+
+    return $fields_data;
+  }
+
+  /**
+   * Returns display and description properties of the specified file.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   * @param string|int $file_id
+   *   The ID of the file.
+   *
+   * @return array
+   *   An array of those properties whose value is not empty.
+   */
+  protected function getFileData(Connection $connection, $file_id): array {
+    foreach ($this->getFileFieldData($connection) as $field_name) {
+      $field_table_name = "field_data_$field_name";
+      $data_query = $connection->select($field_table_name, $field_name);
+      $data_query->addField($field_name, "{$field_name}_display", 'display');
+      $data_query->addField($field_name, "{$field_name}_description", 'description');
+      $data_query->condition("{$field_name}_fid", $file_id);
+
+      if (!empty($results = $data_query->execute()->fetchAll(\PDO::FETCH_ASSOC))) {
+        $result = reset($results);
+        return array_filter($result);
+      }
+    }
+
+    return [];
+  }
+
+}
diff --git a/src/Plugin/media_migration/file_entity/Image.php b/src/Plugin/media_migration/file_entity/Image.php
new file mode 100644
index 0000000..522d743
--- /dev/null
+++ b/src/Plugin/media_migration/file_entity/Image.php
@@ -0,0 +1,311 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\media_migration\file_entity;
+
+use Drupal\Core\Database\Connection;
+use Drupal\media_migration\Plugin\migrate\source\d7\MediaFieldQueryTrait;
+use Drupal\migrate\Row;
+
+/**
+ * Image media migration plugin for local image media entities.
+ *
+ * @FileEntityDealer(
+ *   id = "image",
+ *   types = {"image"},
+ *   destination_media_type_id_base = "image",
+ *   destination_media_source_plugin_id = "image"
+ * )
+ */
+class Image extends FileBase {
+
+  use MediaFieldQueryTrait;
+
+  /**
+   * The map of the alt and title properties and their corresponding field name.
+   *
+   * @var string[]
+   */
+  const PROPERTY_FIELD_NAME_MAP = [
+    'alt' => 'field_file_image_alt_text',
+    'title' => 'field_file_image_title_text',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterMediaEntityMigrationDefinition(array &$migration_definition, Connection $connection): void {
+    parent::alterMediaEntityMigrationDefinition($migration_definition, $connection);
+    $source_field_name = $this->getDestinationMediaSourceFieldName();
+    $migration_definition['process'][$source_field_name . '/width'] = 'width';
+    $migration_definition['process'][$source_field_name . '/height'] = 'height';
+    $migration_definition['process']['thumbnail/target_id'] = 'fid';
+    $migration_definition['process']['thumbnail/width'] = 'width';
+    $migration_definition['process']['thumbnail/height'] = 'height';
+
+    // These property fields only exist when the file_entity module is
+    // installed on the source site.
+    foreach (static::PROPERTY_FIELD_NAME_MAP as $property => $field_name) {
+      $migration_definition['process']["{$property}_from_media"] = [
+        [
+          'plugin' => 'extract',
+          'source' => $field_name,
+          'index' => ['0', 'value'],
+          // It is impossible to set 'NULL' as default value. Using
+          // ['default => NULL'] is equal with not setting the key at all (so
+          // every image media migration that should be migrated from an image
+          // field will be skipped). Setting the default to a predefined
+          // constant that equals to NULL doesn't work either. For example, if
+          // we set 'default' to 'constants/alt_and_title_default', then every
+          // image that's alt is empty will be migrated with the
+          // "constants/alt_and_title_default" string set as alt property. If we
+          // don't set 'default' at all, we will get a 'Notice: Undefined index:
+          // default_value' exception for empty source values.
+          // @todo Revisit after https://www.drupal.org/node/3133516 is
+          //   fixed.
+          'default' => '',
+        ],
+        [
+          'plugin' => 'default_value',
+          'default_value' => NULL,
+        ],
+      ];
+
+      $property_process = [
+        [
+          'plugin' => 'null_coalesce',
+          'source' => [
+            $property,
+            "@{$property}_from_media",
+          ],
+          'default_value' => NULL,
+        ],
+      ];
+
+      $migration_definition['process'][$source_field_name . '/' . $property] =
+      $migration_definition['process']['thumbnail/' . $property] =
+        $property_process;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaEntityRow(Row $row, Connection $connection): void {
+    parent::prepareMediaEntityRow($row, $connection);
+    $file_id = $row->getSourceProperty('fid');
+    // Add width and height source properties for image entities. These
+    // properties only exist when the file_entity module is installed on the
+    // source site.
+    $width_and_height_statement = $connection->select('file_metadata', 'fmd')
+      ->fields('fmd', ['name', 'value'])
+      ->condition('fmd.fid', $file_id)
+      ->condition('fmd.name', ['width', 'height'], 'IN')
+      ->execute()->fetchAll(\PDO::FETCH_ASSOC);
+    foreach ($width_and_height_statement as $result_row) {
+      $row->setSourceProperty($result_row['name'], unserialize($result_row['value']));
+    }
+
+    // Add alt and title properties from image type fields where the current
+    // image is the file value.
+    foreach ($this->getImageData($connection, $file_id) as $data_key => $data_value) {
+      $row->setSourceProperty($data_key, $data_value);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaSourceFieldStorageRow(Row $row, Connection $connection): void {
+    parent::prepareMediaSourceFieldStorageRow($row, $connection);
+    $settings = $row->getSourceProperty('settings');
+    $settings['display_field'] = FALSE;
+    $settings['display_default'] = FALSE;
+    $row->setSourceProperty('settings', $settings);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaSourceFieldInstanceRow(Row $row, Connection $connection): void {
+    parent::prepareMediaSourceFieldInstanceRow($row, $connection);
+
+    // Alt and title properties of an image field always exist, regardless
+    // of whether they are accessible and editable on the entity edit form or
+    // not.
+    // But we also want to make sure that if either the alt or title property
+    // was editable on the source site, then it will be editable also on the
+    // destination site.
+    // For first, we try to determine which properties should be enabled (and
+    // thus accessible and editable on UI) based on the source media image
+    // entities' alt and title fields' content. We assume that if there is at
+    // least a single value for an image's title property in the source
+    // database, then we have to change visibility of the title property to
+    // TRUE, so that it can be edited on the destination site as well.
+    $alt_title_config = $this->getImageAltTitleSettingsFromPropertyFieldContent($connection);
+    // All image field content is also migrated into a media entity. If any of
+    // the alt or title properties shouldn't be necessarily shown (and be
+    // editable) on the entity edit form based on the source media entity's
+    // alt or title field value, we still have to check the configuration of
+    // the source site's image fields.
+    // If any of the preexisting image field was configured to show the alt or
+    // the title property, then we will make their input field visible.
+    $props = array_map(function (string $property) {
+      return "{$property}_field";
+    }, array_keys(static::PROPERTY_FIELD_NAME_MAP));
+    if (!empty(array_diff($props, array_keys($alt_title_config)))) {
+      $alt_title_config += $this->getSettingsFromImageFields($connection);
+    }
+
+    // Get the 'required' settings from the image fields we found.
+    $this->mergePropertyRequiredSettingsFromImageFields($alt_title_config, $connection);
+
+    // Add the discovered and the default configuration.
+    $additional_properties = $alt_title_config + [
+      'alt_field' => TRUE,
+      'alt_field_required' => TRUE,
+      'title_field' => FALSE,
+      'title_field_required' => FALSE,
+    ];
+
+    foreach ($additional_properties as $source_property => $source_value) {
+      $row->setSourceProperty($source_property, $source_value);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaSourceFieldFormatterRow(Row $row, Connection $connection): void {
+    parent::prepareMediaSourceFieldFormatterRow($row, $connection);
+    $options = $row->getSourceProperty('options') ?? [];
+    $options['settings'] = [
+      'image_style' => 'large',
+    ];
+    $row->setSourceProperty('options', $options);
+  }
+
+  /**
+   * Discovers the image field settings based on existing property values.
+   *
+   * The alt and title properties of an image field always exist, regardless
+   * of whether they are actually accessible and editable on the entity edit
+   * form or not. This method checks the content of the corresponding alt and
+   * title fields. If we find at least a single, non-empty row, we say that the
+   * actual property should be shown on the destination media entity's edit
+   * form.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   *
+   * @return true[]
+   *   An array of those field instance settings that should be revealed, keyed
+   *   by the settings key ("alt_field", "title_field").
+   *   For example, if we have rows for alt, but don't have any data for title,
+   *   the array returned will be this:
+   *   @code
+   *   [
+   *     "alt_field" => TRUE
+   *   ]
+   *   @endcode
+   */
+  protected function getImageAltTitleSettingsFromPropertyFieldContent(Connection $connection): array {
+    $data = [];
+
+    foreach (static::PROPERTY_FIELD_NAME_MAP as $property => $field_name) {
+      $property_values_query = $connection->select("field_data_$field_name", $field_name)
+        ->fields($field_name)
+        ->condition("$field_name.{$field_name}_value", '', '<>')
+        ->isNotNull("$field_name.{$field_name}_value");
+      $property_values_present = (int) $property_values_query->countQuery()->execute()->fetchField() > 0;
+
+      if ($property_values_present) {
+        $data["{$property}_field"] = TRUE;
+      }
+    }
+
+    return $data;
+  }
+
+  /**
+   * Discovers enabled properties based on the image field configurations.
+   *
+   * The alt and title properties of an image field always exist, regardless
+   * of that they are actually accessible and editable on the entity edit form.
+   * This method checks the config of every image field's instance configuration
+   * When alt (or title) was enabled for at least one image field, we say that
+   * the property should be shown on the destination media entity's edit
+   * form.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   *
+   * @return array
+   *   An array of those settings that should be revealed, keyed by the settings
+   *   key ('alt_field', 'title_field').
+   */
+  protected function getSettingsFromImageFields(Connection $connection): array {
+    $data = [];
+    $image_field_names = $this->getImageFieldNames($connection);
+
+    foreach ($image_field_names as $image_field_name) {
+      $field_instance_config_results = $connection->select('field_config_instance', 'fci')
+        ->fields('fci', ['data'])
+        ->condition('fci.field_name', $image_field_name)
+        ->condition('fci.entity_type', 'file', '<>')
+        ->execute()
+        ->fetchAll();
+
+      foreach ($field_instance_config_results as $field_instance_config_result) {
+        $field_config_data = unserialize($field_instance_config_result->data);
+        $props = array_map(function (string $property) {
+          return "{$property}_field";
+        }, array_keys(static::PROPERTY_FIELD_NAME_MAP));
+
+        foreach ($props as $property) {
+          if (isset($field_config_data['settings'][$property]) && !empty($field_config_data['settings'][$property])) {
+            $data[$property] = TRUE;
+          }
+        }
+
+        if (empty(array_diff($props, array_keys($data)))) {
+          break 2;
+        }
+      }
+    }
+
+    return $data;
+  }
+
+  /**
+   * Merges alt/title 'required' settings based on image field discovery.
+   *
+   * If any image field have alt (or title) set up as optional, we don't let
+   * them being required.
+   *
+   * @param array $data
+   *   The discovered data about alt and title revealment.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection of the source Drupal 7 instance.
+   */
+  protected function mergePropertyRequiredSettingsFromImageFields(array &$data, Connection $connection): void {
+    foreach (static::PROPERTY_FIELD_NAME_MAP as $property => $field_name) {
+      $property_field_config_result = $connection->select('field_config_instance', 'fci')
+        ->fields('fci', ['data'])
+        ->condition('fci.field_name', $field_name)
+        ->condition('fci.bundle', 'image')
+        ->condition('fci.entity_type', 'file')
+        ->execute()
+        ->fetchAll();
+
+      assert(count($property_field_config_result) === 1);
+
+      if ($property_field_config_data = unserialize($property_field_config_result[0]->data)) {
+        if (isset($property_field_config_data['required'])) {
+          $not_set_or_not_required = !isset($data["{$property}_field_required"]) || empty($data["{$property}_field_required"]);
+          $data["{$property}_field_required"] = $not_set_or_not_required && !empty($data["{$property}_field"]) && !empty($property_field_config_data['required']);
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/Plugin/media_migration/file_entity/RemoteVideoBase.php b/src/Plugin/media_migration/file_entity/RemoteVideoBase.php
new file mode 100644
index 0000000..9a18fe5
--- /dev/null
+++ b/src/Plugin/media_migration/file_entity/RemoteVideoBase.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\media_migration\file_entity;
+
+use Drupal\Core\Database\Connection;
+use Drupal\media_migration\FileEntityDealerBase;
+use Drupal\migrate\Row;
+
+/**
+ * Abstract plugin class for remote video media migration source plugins.
+ */
+abstract class RemoteVideoBase extends FileEntityDealerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaTypeId() {
+    return 'remote_video';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaTypeSourceFieldLabel() {
+    return 'Video URL';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaTypeLabel() {
+    return 'Remote video';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaSourceFieldName() {
+    return 'field_media_oembed_video';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterMediaEntityMigrationDefinition(array &$migration_definition, Connection $connection): void {
+    $migration_definition['process'][$this->getDestinationMediaSourceFieldName() . '/value'] = [
+      'plugin' => 'media_internet_field_value',
+      'source' => 'uri',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaSourceFieldFormatterRow(Row $row, Connection $connection): void {
+    parent::prepareMediaSourceFieldFormatterRow($row, $connection);
+    $options = [
+      'type' => 'oembed',
+    ] + $row->getSourceProperty('options') ?? [];
+    $row->setSourceProperty('options', $options);
+  }
+
+}
diff --git a/src/Plugin/media_migration/file_entity/Video.php b/src/Plugin/media_migration/file_entity/Video.php
new file mode 100644
index 0000000..1b62c32
--- /dev/null
+++ b/src/Plugin/media_migration/file_entity/Video.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\media_migration\file_entity;
+
+use Drupal\Core\Database\Connection;
+use Drupal\migrate\Row;
+
+/**
+ * Video media migration plugin for local video media entities.
+ *
+ * @FileEntityDealer(
+ *   id = "video",
+ *   types = {"video"},
+ *   destination_media_type_id_base = "video",
+ *   destination_media_source_plugin_id = "video_file"
+ * )
+ */
+class Video extends FileBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaTypeSourceFieldLabel() {
+    return 'Video file';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDestinationMediaTypeLabel() {
+    return implode(' ', array_filter([
+      'Video',
+      $this->configuration['scheme'] === 'public' ? NULL : "({$this->configuration['scheme']})",
+    ]));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareMediaSourceFieldFormatterRow(Row $row, Connection $connection): void {
+    parent::prepareMediaSourceFieldFormatterRow($row, $connection);
+    $options = [
+      'type' => 'file_video',
+      'settings' => [
+        'muted' => FALSE,
+        'width' => 640,
+        'height' => 480,
+      ],
+    ] + $row->getSourceProperty('options') ?? [];
+    $row->setSourceProperty('options', $options);
+  }
+
+}
diff --git a/src/Plugin/media_migration/file_entity/Vimeo.php b/src/Plugin/media_migration/file_entity/Vimeo.php
new file mode 100644
index 0000000..48814ff
--- /dev/null
+++ b/src/Plugin/media_migration/file_entity/Vimeo.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\media_migration\file_entity;
+
+/**
+ * Vimeo media migration plugin for Vimeo media entities.
+ *
+ * @FileEntityDealer(
+ *   id = "vimeo",
+ *   types = {"video"},
+ *   schemes = {"vimeo"},
+ *   destination_media_source_plugin_id = "oembed:video"
+ * )
+ */
+class Vimeo extends RemoteVideoBase {
+
+}
diff --git a/src/Plugin/media_migration/file_entity/Youtube.php b/src/Plugin/media_migration/file_entity/Youtube.php
new file mode 100644
index 0000000..de9c72c
--- /dev/null
+++ b/src/Plugin/media_migration/file_entity/Youtube.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\media_migration\file_entity;
+
+/**
+ * Youtube media migration plugin for YouTube media entities.
+ *
+ * @FileEntityDealer(
+ *   id = "youtube",
+ *   types = {"video"},
+ *   schemes = {"youtube"},
+ *   destination_media_source_plugin_id = "oembed:video"
+ * )
+ */
+class Youtube extends RemoteVideoBase {
+
+}
diff --git a/src/Plugin/migrate/D7FileEntityConfigDeriver.php b/src/Plugin/migrate/D7FileEntityConfigDeriver.php
new file mode 100644
index 0000000..88eebe6
--- /dev/null
+++ b/src/Plugin/migrate/D7FileEntityConfigDeriver.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\migrate;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Database\DatabaseExceptionWrapper;
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\media_migration\FileEntityDealerManagerInterface;
+use Drupal\media_migration\FileEntityDealerPluginInterface;
+use Drupal\media_migration\Plugin\migrate\source\d7\FileEntityConfigSourceBase;
+use Drupal\migrate\Exception\RequirementsException;
+use Drupal\migrate\Plugin\MigrationDeriverTrait;
+use Drupal\migrate\Row;
+use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Deriver for file entities' configuration entity migrations.
+ */
+class D7FileEntityConfigDeriver extends DeriverBase implements ContainerDeriverInterface {
+
+  use MigrationDeriverTrait;
+  use StringTranslationTrait;
+
+  /**
+   * The file entity dealer plugin manager.
+   *
+   * @var \Drupal\media_migration\FileEntityDealerManagerInterface
+   */
+  protected $fileEntityDealerManager;
+
+  /**
+   * D7FileEntityConfigDeriver constructor.
+   *
+   * @param \Drupal\media_migration\FileEntityDealerManagerInterface $file_entity_dealer_manager
+   *   The file entity dealer plugin manager.
+   */
+  public function __construct(FileEntityDealerManagerInterface $file_entity_dealer_manager) {
+    $this->fileEntityDealerManager = $file_entity_dealer_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static(
+      $container->get('plugin.manager.file_entity_dealer')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    $file_entity_types = static::getSourcePlugin('d7_file_entity_type');
+
+    try {
+      $file_entity_types->checkRequirements();
+    }
+    catch (RequirementsException $e) {
+      return $this->derivatives;
+    }
+
+    assert($file_entity_types instanceof DrupalSqlBase);
+
+    try {
+      foreach ($file_entity_types as $file_entity_type_row) {
+        assert($file_entity_type_row instanceof Row);
+        [
+          'types' => $types,
+          'schemes' => $schemes,
+        ] = $file_entity_type_row->getSource();
+
+        // Multiple types and schemes may have the same destination media type
+        // ID.
+        $type = explode(FileEntityConfigSourceBase::MULTIPLE_SEPARATOR, $types)[0];
+        $scheme = explode(FileEntityConfigSourceBase::MULTIPLE_SEPARATOR, $schemes)[0];
+        $dealer_plugin = $this->fileEntityDealerManager->createInstanceFromTypeAndScheme($type, $scheme);
+
+        // No plugin was found for this file entity type row.
+        if (!($dealer_plugin instanceof FileEntityDealerPluginInterface)) {
+          throw new \LogicException(sprintf('No FileEntityDealer plugin applies for file entities with type "%s" and with scheme "%s/*"', [$type, $scheme]));
+        }
+
+        $destination_media_type_id = $dealer_plugin->getDestinationMediaTypeId();
+        $source_plugin_id = $base_plugin_definition['source']['plugin'];
+        $derivative_definition = $base_plugin_definition;
+        // Create the migration derivative.
+        $derivative_definition['source']['schemes'] = $schemes;
+        $derivative_definition['source']['types'] = $types;
+        $derivative_definition['label'] = $this->t('@label (@type)', [
+          '@label' => $base_plugin_definition['label'],
+          '@type' => $dealer_plugin->getDestinationMediaTypeLabel(),
+        ]);
+
+        // Post-process migration dependencies: instead of depending on
+        // migrations based on their base plugin ID, it is better to use the
+        // corresponding derivatives where possible.
+        $derived_migration_ids = [
+          'd7_file_entity_type',
+          'd7_file_entity_source_field',
+          'd7_file_entity_source_field_config',
+          'd7_file_entity_widget',
+          'd7_file_entity_formatter',
+        ];
+        foreach ($derived_migration_ids as $derived_migration_base_id) {
+          $required_dependencies = !empty($derivative_definition['migration_dependencies']['required'])
+            ? $derivative_definition['migration_dependencies']['required']
+            : [];
+          $dependency_key = array_search($derived_migration_base_id, $required_dependencies, TRUE);
+          if ($dependency_key !== FALSE) {
+            $derivative_definition['migration_dependencies']['required'][$dependency_key] .= ":$destination_media_type_id";
+          }
+        }
+
+        switch ($source_plugin_id) {
+          case 'd7_file_entity_type':
+            $dealer_plugin->alterMediaTypeMigrationDefinition($derivative_definition, $file_entity_types->getDatabase());
+            break;
+
+          case 'd7_file_entity_source_field_storage':
+            $dealer_plugin->alterMediaSourceFieldStorageMigrationDefinition($derivative_definition, $file_entity_types->getDatabase());
+            break;
+
+          case 'd7_file_entity_source_field_instance':
+            $dealer_plugin->alterMediaSourceFieldInstanceMigrationDefinition($derivative_definition, $file_entity_types->getDatabase());
+            break;
+
+          case 'd7_file_entity_field_widget':
+            $dealer_plugin->alterMediaSourceFieldWidgetMigrationDefinition($derivative_definition, $file_entity_types->getDatabase());
+            break;
+
+          case 'd7_file_entity_field_formatter':
+            $dealer_plugin->alterMediaFieldFormatterMigrationDefinition($derivative_definition, $file_entity_types->getDatabase());
+            break;
+        }
+
+        $this->derivatives[$destination_media_type_id] = $derivative_definition;
+      }
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      // Once we begin iterating the source plugin it is possible that the
+      // source tables will not exist. This can happen when the
+      // MigrationPluginManager gathers up the migration definitions but we do
+      // not actually have a Drupal 7 source database.
+    }
+    return $this->derivatives;
+  }
+
+}
diff --git a/src/Plugin/migrate/D7FileEntityDeriver.php b/src/Plugin/migrate/D7FileEntityDeriver.php
new file mode 100644
index 0000000..ea05e7d
--- /dev/null
+++ b/src/Plugin/migrate/D7FileEntityDeriver.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\migrate;
+
+use Drupal\Core\Database\DatabaseExceptionWrapper;
+use Drupal\media_migration\FileEntityDealerManagerInterface;
+use Drupal\media_migration\FileEntityDealerPluginInterface;
+use Drupal\media_migration\Plugin\migrate\source\d7\FileEntityConfigSourceBase;
+use Drupal\migrate\Exception\RequirementsException;
+use Drupal\migrate\Plugin\Migration;
+use Drupal\migrate\Row;
+use Drupal\migrate_drupal\FieldDiscoveryInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Deriver for file entity migrations.
+ */
+class D7FileEntityDeriver extends D7FileEntityConfigDeriver {
+
+  /**
+   * The base plugin ID this derivative is for.
+   *
+   * @var string
+   */
+  protected $basePluginId;
+
+  /**
+   * The field plugin manager.
+   *
+   * @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface
+   */
+  protected $fieldPluginManager;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The migration field discovery service.
+   *
+   * @var \Drupal\migrate_drupal\FieldDiscoveryInterface
+   */
+  protected $fieldDiscovery;
+
+  /**
+   * The file entity dealer plugin manager.
+   *
+   * @var \Drupal\media_migration\FileEntityDealerManagerInterface
+   */
+  protected $fileEntityDealerManager;
+
+  /**
+   * D7FileEntityDeriver constructor.
+   *
+   * @param \Drupal\media_migration\FileEntityDealerManagerInterface $file_entity_dealer_manager
+   *   The file entity dealer plugin manager.
+   * @param \Drupal\migrate_drupal\FieldDiscoveryInterface $field_discovery
+   *   The migration field discovery service.
+   */
+  public function __construct(FileEntityDealerManagerInterface $file_entity_dealer_manager, FieldDiscoveryInterface $field_discovery) {
+    parent::__construct($file_entity_dealer_manager);
+    $this->fieldDiscovery = $field_discovery;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static(
+      $container->get('plugin.manager.file_entity_dealer'),
+      $container->get('migrate_drupal.field_discovery')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    $file_entity_types = static::getSourcePlugin('d7_file_entity_type');
+
+    try {
+      $file_entity_types->checkRequirements();
+    }
+    catch (RequirementsException $e) {
+      return $this->derivatives;
+    }
+
+    try {
+      foreach ($file_entity_types as $file_entity_type_row) {
+        assert($file_entity_type_row instanceof Row);
+        [
+          'types' => $types,
+          'schemes' => $schemes,
+        ] = $file_entity_type_row->getSource();
+
+        foreach (explode(FileEntityConfigSourceBase::MULTIPLE_SEPARATOR, $types) as $type) {
+          foreach (explode(FileEntityConfigSourceBase::MULTIPLE_SEPARATOR, $schemes) as $scheme) {
+            $dealer_plugin = $this->fileEntityDealerManager->createInstanceFromTypeAndScheme($type, $scheme);
+
+            // No plugin was found for this file entity type row.
+            if (!($dealer_plugin instanceof FileEntityDealerPluginInterface)) {
+              throw new \LogicException(sprintf('No FileEntityDealer plugin applies for file entities with type "%s" and with scheme "%s/*"', [$type, $scheme]));
+            }
+
+            $destination_media_type_id = $dealer_plugin->getDestinationMediaTypeId();
+            $derivative_definition = $base_plugin_definition;
+            $derivative_id = "$type:$scheme";
+            // Create the migration derivative.
+            $derivative_definition['source']['type'] = $type;
+            $derivative_definition['source']['scheme'] = $scheme;
+            $derivative_definition['label'] = $this->t('@label (@type)', [
+              '@label' => $base_plugin_definition['label'],
+              '@type' => $dealer_plugin->getDestinationMediaTypeLabel(),
+            ]);
+
+            // Post-process migration dependencies: instead of depending on
+            // migrations based on their base plugin ID, it is better to use the
+            // corresponding derivatives where possible.
+            $derived_migration_ids = [
+              'd7_file_entity_type',
+              'd7_file_entity_source_field',
+              'd7_file_entity_source_field_config',
+            ];
+            foreach ($derived_migration_ids as $derived_migration_base_id) {
+              $required_dependencies = !empty($derivative_definition['migration_dependencies']['required'])
+                ? $derivative_definition['migration_dependencies']['required']
+                : [];
+              $dependency_key = array_search($derived_migration_base_id, $required_dependencies, TRUE);
+              if ($dependency_key !== FALSE) {
+                $derivative_definition['migration_dependencies']['required'][$dependency_key] .= ":$destination_media_type_id";
+              }
+            }
+
+            // Add bundle field processes.
+            $migration = \Drupal::service('plugin.manager.migration')->createStubMigration($derivative_definition);
+            $process_keys_before = array_keys($derivative_definition['process']);
+            assert($migration instanceof Migration);
+            $this->fieldDiscovery->addBundleFieldProcesses($migration, 'file', $type);
+            $derivative_definition = $migration->getPluginDefinition();
+            if (!empty(array_diff(array_keys($derivative_definition['process']), $process_keys_before))) {
+              $derivative_definition['migration_dependencies']['required'][] = 'd7_field_instance';
+            }
+
+            $dealer_plugin->alterMediaEntityMigrationDefinition($derivative_definition, $file_entity_types->getDatabase());
+
+            $this->derivatives[$derivative_id] = $derivative_definition;
+          }
+        }
+      }
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      // Once we begin iterating the source plugin it is possible that the
+      // source tables will not exist. This can happen when the
+      // MigrationPluginManager gathers up the migration definitions but we do
+      // not actually have a Drupal 7 source database.
+    }
+    return $this->derivatives;
+  }
+
+}
diff --git a/src/Plugin/migrate/D7FileEntityItemDeriver.php b/src/Plugin/migrate/D7FileEntityItemDeriver.php
deleted file mode 100644
index a38324a..0000000
--- a/src/Plugin/migrate/D7FileEntityItemDeriver.php
+++ /dev/null
@@ -1,268 +0,0 @@
-<?php
-
-namespace Drupal\media_migration\Plugin\migrate;
-
-use Drupal\Component\Plugin\Derivative\DeriverBase;
-use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
-use Drupal\Component\Plugin\Exception\PluginNotFoundException;
-use Drupal\Core\Database\DatabaseExceptionWrapper;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\media\MediaTypeInterface;
-use Drupal\migrate\Exception\RequirementsException;
-use Drupal\migrate\Plugin\MigratePluginManagerInterface;
-use Drupal\migrate\Plugin\MigrationDeriverTrait;
-use Drupal\migrate_drupal\FieldDiscoveryInterface;
-use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Deriver for file entities.
- */
-class D7FileEntityItemDeriver extends DeriverBase implements ContainerDeriverInterface {
-
-  use MigrationDeriverTrait;
-  use StringTranslationTrait;
-
-  /**
-   * The base plugin ID this derivative is for.
-   *
-   * @var string
-   */
-  protected $basePluginId;
-
-  /**
-   * Already-instantiated field plugins, keyed by ID.
-   *
-   * @var \Drupal\migrate_drupal\Plugin\MigrateFieldInterface[]
-   */
-  protected $fieldPluginCache;
-
-  /**
-   * The field plugin manager.
-   *
-   * @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface
-   */
-  protected $fieldPluginManager;
-
-  /**
-   * The entity type manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
-   * The migration field discovery service.
-   *
-   * @var \Drupal\migrate_drupal\FieldDiscoveryInterface
-   */
-  protected $fieldDiscovery;
-
-  /**
-   * The ID of the null coalesce migrate process plugin to use.
-   *
-   * The 'null_coalesce' plugin was introduced in Drupal core 8.8.x, but we
-   * need it for being able to properly handle image media alt and title
-   * migrations. So we define our own, equal plugin; but we use it only if the
-   * 'null_coalesce' from core is not available.
-   *
-   * @var string
-   *
-   * @todo Remove when Drupal core 8.7.x security support ends.
-   */
-  protected $nullCoalesceMigrateProcessPluginId;
-
-  /**
-   * D7FileEntityItemDeriver constructor.
-   *
-   * @param string $base_plugin_id
-   *   The base plugin ID for the plugin ID.
-   * @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $field_manager
-   *   The field plugin manager.
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   *   The entity type manager.
-   * @param \Drupal\migrate_drupal\FieldDiscoveryInterface $field_discovery
-   *   The migration field discovery service.
-   * @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $process_plugin_manager
-   *   The process migration plugin manager.
-   */
-  public function __construct($base_plugin_id, MigrateFieldPluginManagerInterface $field_manager, EntityTypeManagerInterface $entity_type_manager, FieldDiscoveryInterface $field_discovery, MigratePluginManagerInterface $process_plugin_manager) {
-    $this->basePluginId = $base_plugin_id;
-    $this->fieldPluginManager = $field_manager;
-    $this->entityTypeManager = $entity_type_manager;
-    $this->fieldDiscovery = $field_discovery;
-    $this->nullCoalesceMigrateProcessPluginId = $process_plugin_manager->hasDefinition('null_coalesce') ?
-      'null_coalesce' :
-      'media_migration_null_coalesce';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, $base_plugin_id) {
-    return new static(
-      $base_plugin_id,
-      $container->get('plugin.manager.migrate.field'),
-      $container->get('entity_type.manager'),
-      $container->get('migrate_drupal.field_discovery'),
-      $container->get('plugin.manager.migrate.process')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDerivativeDefinitions($base_plugin_definition) {
-    $types = static::getSourcePlugin('d7_file_entity_type');
-
-    try {
-      $types->checkRequirements();
-    }
-    catch (RequirementsException $e) {
-      return $this->derivatives;
-    }
-
-    try {
-      foreach ($types as $row) {
-        /** @var \Drupal\migrate\Row $row */
-        $derivative_definition = $base_plugin_definition;
-        $bundle_name = $row->getSourceProperty('type');
-        $derivative_id = $bundle_name;
-
-        $media_type = NULL;
-        try {
-          $media_type = $this->entityTypeManager->getStorage('media_type')
-            ->load($bundle_name);
-        }
-        // Catch plugin not found and invalid plugin definition exceptions.
-        catch (PluginNotFoundException $e) {
-          continue;
-        }
-        catch (InvalidPluginDefinitionException $e) {
-          continue;
-        }
-
-        // If this media type does not exist yet or the variable not a media
-        // type, continue to the next one.
-        if (empty($media_type) || !($media_type instanceof MediaTypeInterface)) {
-          continue;
-        }
-
-        $source_field_name = $media_type->getSource()->getConfiguration()['source_field'];
-
-        // Create the migration derivative.
-        $derivative_definition['source']['type'] = $bundle_name;
-        $derivative_definition['label'] = $this->t('@label (@type)', [
-          '@label' => $base_plugin_definition['label'],
-          '@type' => $media_type->label(),
-        ]);
-
-        // Media Migration uses this derivative class for multiple migrations,
-        // but the actual media entity migration derivatives need special
-        // treatment.
-        if ($base_plugin_definition['source']['plugin'] === 'd7_file_entity_item') {
-          $derivative_definition['destination']['bundle'] = $bundle_name;
-          $derivative_definition['process'][$source_field_name . '/target_id'] = 'fid';
-
-          // Map width and height for image entities.
-          if ($bundle_name == 'image') {
-            $derivative_definition['process'][$source_field_name . '/width'] = 'width';
-            $derivative_definition['process'][$source_field_name . '/height'] = 'height';
-            $derivative_definition['process']['thumbnail/target_id'] = 'fid';
-            $derivative_definition['process']['thumbnail/width'] = 'width';
-            $derivative_definition['process']['thumbnail/height'] = 'height';
-          }
-
-          /** @var \Drupal\migrate\Plugin\Migration $migration */
-          $migration = \Drupal::service('plugin.manager.migration')
-            ->createStubMigration($derivative_definition);
-          $this->fieldDiscovery->addBundleFieldProcesses($migration, 'file', $bundle_name);
-          $derivative_definition = $migration->getPluginDefinition();
-
-          if ($media_type->getSource()->getPluginId() === 'image') {
-            // These property fields only exist when the file_entity module is
-            // installed on the source site.
-            $property_fields = [
-              'field_file_image_alt_text' => 'alt',
-              'field_file_image_title_text' => 'title',
-            ];
-
-            foreach ($property_fields as $field_name => $property) {
-              $derivative_definition['process']["{$property}_from_media"] = [
-                [
-                  'plugin' => 'extract',
-                  'source' => $field_name,
-                  'index' => ['0', 'value'],
-                  // It seems to be impossible to set 'NULL' as default value.
-                  // Using ['default => NULL'] is equal with not setting the key
-                  // at all (so every image media migraqqqtion that should be
-                  // migrated from an image field will be skipped).
-                  // Setting the default to a predefined constant that equals to
-                  // NULL doesn't work either. For example, if we set 'default'
-                  // to 'constants/alt_and_title_default', then every image
-                  // that's alt is empty will be migrated with the
-                  // 'constants/alt_and_title_default' string set as alt
-                  // property. If we don't set 'default' at all, we will get a
-                  // 'Notice: Undefined index: default_value' exception for
-                  // empty source values.
-                  // @todo Revisit after https://www.drupal.org/node/3133516 is
-                  //   fixed.
-                  'default' => '',
-                ],
-                [
-                  'plugin' => 'default_value',
-                  'default_value' => NULL,
-                ],
-              ];
-
-              $property_process = [
-                [
-                  'plugin' => $this->nullCoalesceMigrateProcessPluginId,
-                  'source' => [
-                    "image_field_$property",
-                    "@{$property}_from_media",
-                  ],
-                  'default_value' => NULL,
-                ],
-              ];
-
-              $derivative_definition['process'][$source_field_name . '/' . $property] =
-              $derivative_definition['process']['thumbnail/' . $property] =
-                $property_process;
-            }
-          }
-        }
-
-        // Post-process migration dependencies: instead of depending on
-        // migrations based on their base plugin ID, it is better to use the
-        // corresponding derivatives where possible.
-        $derived_migration_ids = [
-          'd7_file_entity',
-          'd7_file_entity_type',
-          'd7_media_source_field_config',
-        ];
-        foreach ($derived_migration_ids as $derived_migration_base_id) {
-          $required_dependencies = !empty($derivative_definition['migration_dependencies']['required'])
-            ? $derivative_definition['migration_dependencies']['required']
-            : [];
-          $dependency_key = array_search($derived_migration_base_id, $required_dependencies, TRUE);
-          if ($dependency_key !== FALSE) {
-            $derivative_definition['migration_dependencies']['required'][$dependency_key] .= ":$bundle_name";
-          }
-        }
-
-        $this->derivatives[$derivative_id] = $derivative_definition;
-      }
-    }
-    catch (DatabaseExceptionWrapper $e) {
-      // Once we begin iterating the source plugin it is possible that the
-      // source tables will not exist. This can happen when the
-      // MigrationPluginManager gathers up the migration definitions but we do
-      // not actually have a Drupal 7 source database.
-    }
-    return $this->derivatives;
-  }
-
-}
diff --git a/src/Plugin/migrate/process/FileEntityFieldInstanceSettings.php b/src/Plugin/migrate/process/FileEntityFieldInstanceSettings.php
index 520fad0..d84ba89 100644
--- a/src/Plugin/migrate/process/FileEntityFieldInstanceSettings.php
+++ b/src/Plugin/migrate/process/FileEntityFieldInstanceSettings.php
@@ -29,18 +29,6 @@ class FileEntityFieldInstanceSettings extends MediaMigrationFieldInstanceSetting
         ? array_filter($widget_settings['settings']['allowed_types'])
         : [];
 
-      // We have to map "file" type to "document" for sites installed with
-      // Drupal core version prior 8.8.0.
-      $document_type_is_missing = !in_array('document', $media_type_ids, TRUE);
-      $file_type_is_available = in_array('file', $media_type_ids, TRUE);
-      if ($document_type_is_missing && $file_type_is_available) {
-        $document_key = array_search('document', $target_bundles);
-        if ($document_key !== FALSE) {
-          unset($target_bundles[$document_key]);
-          $target_bundles['file'] = 'file';
-        }
-      }
-
       // In Drupal 7, when no media types are explicitly enabled on this field,
       // that means that every media type is allowed. In Drupal 8|9, this
       // feature is not available anymore: "target_bundles" cannot be empty.
diff --git a/src/Plugin/migrate/process/MediaInternetFieldValue.php b/src/Plugin/migrate/process/MediaInternetFieldValue.php
new file mode 100644
index 0000000..791f55a
--- /dev/null
+++ b/src/Plugin/migrate/process/MediaInternetFieldValue.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\migrate\process;
+
+use Drupal\migrate\MigrateExecutableInterface;
+use Drupal\migrate\ProcessPluginBase;
+use Drupal\migrate\Row;
+
+/**
+ * Processes and returns media internet field values.
+ *
+ * @MigrateProcessPlugin(
+ *   id = "media_internet_field_value",
+ *   handle_multiples = TRUE
+ * )
+ */
+class MediaInternetFieldValue extends ProcessPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
+    // We're operating with the source values.
+    $source_values = $row->getSource();
+    $replaced = preg_replace([
+      '/^youtube:\/\/v\//i',
+      '/^vimeo:\/\/v\//i',
+    ], [
+      'https://www.youtube.com/watch?v=',
+      'https://vimeo.com/',
+    ], $source_values['uri']);
+
+    if ($replaced !== $source_values['uri']) {
+      return $replaced;
+    }
+
+    return $value;
+  }
+
+}
diff --git a/src/Plugin/migrate/process/MediaMigrationFieldInstanceSettingsProcessPluginBase.php b/src/Plugin/migrate/process/MediaMigrationFieldInstanceSettingsProcessPluginBase.php
index 6fb5084..40d9a07 100644
--- a/src/Plugin/migrate/process/MediaMigrationFieldInstanceSettingsProcessPluginBase.php
+++ b/src/Plugin/migrate/process/MediaMigrationFieldInstanceSettingsProcessPluginBase.php
@@ -74,28 +74,7 @@ abstract class MediaMigrationFieldInstanceSettingsProcessPluginBase extends Proc
     try {
       foreach ($media_type_file_entity_source as $row) {
         assert($row instanceof Row);
-        $data = $row->getSource();
-        $data['bundle'] = $data['type'] ?? NULL;
-        switch ($data['type']) {
-          case 'audio':
-            $source_plugin_id = 'audio_file';
-            break;
-
-          case 'image':
-            $source_plugin_id = 'image';
-            break;
-
-          case 'video':
-            $source_plugin_id = 'video_file';
-            break;
-
-          case 'document':
-          default:
-            $source_plugin_id = 'file';
-            break;
-        }
-        $data['source_plugin_id'] = $source_plugin_id;
-        $media_types[$data['bundle']] = $media_types[$data['bundle']] ?? $data;
+        $media_types[$row->getSourceProperty('bundle')] = $media_types[$row->getSourceProperty('bundle')] ?? $row->getSource();
       }
     }
     catch (DatabaseExceptionWrapper $e) {
diff --git a/src/Plugin/migrate/source/d7/FileEntityConfigSourceBase.php b/src/Plugin/migrate/source/d7/FileEntityConfigSourceBase.php
new file mode 100644
index 0000000..64b7aac
--- /dev/null
+++ b/src/Plugin/migrate/source/d7/FileEntityConfigSourceBase.php
@@ -0,0 +1,176 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\migrate\source\d7;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\State\StateInterface;
+use Drupal\media_migration\FileEntityDealerManagerInterface;
+use Drupal\migrate\Plugin\MigrationInterface;
+use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Class FileEntityConfigSourceBase.
+ */
+abstract class FileEntityConfigSourceBase extends DrupalSqlBase {
+
+  use MediaMigrationDatabaseTrait;
+
+  const MULTIPLE_SEPARATOR = '::';
+
+  /**
+   * The file entity dealer plugin manager.
+   *
+   * @var \Drupal\media_migration\FileEntityDealerManagerInterface
+   */
+  protected $fileEntityDealerManager;
+
+  /**
+   * Constructs a plugin instance.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\migrate\Plugin\MigrationInterface $migration
+   *   The current migration.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\media_migration\FileEntityDealerManagerInterface $file_entity_dealer_manager
+   *   The file entity dealer plugin manager.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager, FileEntityDealerManagerInterface $file_entity_dealer_manager) {
+    $configuration += [
+      'types' => NULL,
+      'schemes' => NULL,
+    ];
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager);
+    $this->fileEntityDealerManager = $file_entity_dealer_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $migration,
+      $container->get('state'),
+      $container->get('entity_type.manager'),
+      $container->get('plugin.manager.file_entity_dealer')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    [
+      'types' => $types,
+      'schemes' => $schemes,
+    ] = $this->configuration;
+
+    $query = $this->getFileEntityBaseQuery();
+
+    if ($types) {
+      $query->condition('fm.type', explode(self::MULTIPLE_SEPARATOR, $types), 'IN');
+    }
+
+    if ($schemes) {
+      $query->where("{$this->getSchemeExpression()} IN (:schemes[])", [
+        ':schemes[]' => explode(self::MULTIPLE_SEPARATOR, $schemes),
+      ]);
+    }
+
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function prepareQuery() {
+    parent::prepareQuery();
+
+    $this->query->addTag('migrate__media_migration');
+    $this->query->addTag('migrate__media_migration__file_entity');
+    $this->query->addTag('migrate__media_migration__media_configuration');
+    $this->query->addTag("migrate__media_migration__source__{$this->pluginId}");
+
+    return $this->query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function initializeIterator() {
+    $results = $this->prepareQuery()->execute()->fetchAll();
+    $rows = [];
+    foreach ($results as $result) {
+      [
+        'type' => $type,
+        'scheme' => $scheme,
+      ] = $result;
+
+      if (!($dealer_plugin = $this->fileEntityDealerManager->createInstanceFromTypeAndScheme($type, $scheme))) {
+        continue;
+      }
+
+      $destination_media_type_id = $dealer_plugin->getDestinationMediaTypeId();
+      $source_values = $rows[$destination_media_type_id] ?? $result + [
+        'types' => $type,
+        'schemes' => $scheme,
+      ];
+
+      $source_values['types'] = implode(self::MULTIPLE_SEPARATOR, array_unique(array_merge(explode(self::MULTIPLE_SEPARATOR, $source_values['types']), [$type])));
+      $source_values['schemes'] = implode(self::MULTIPLE_SEPARATOR, array_unique(array_merge(explode(self::MULTIPLE_SEPARATOR, $source_values['schemes']), [$scheme])));
+      $source_values['bundle'] = $destination_media_type_id;
+      $source_values['bundle_label'] = $dealer_plugin->getDestinationMediaTypeLabel();
+      $source_values['source_plugin_id'] = $dealer_plugin->getDestinationMediaSourcePluginId();
+      $source_values['source_field_name'] = $dealer_plugin->getDestinationMediaSourceFieldName();
+      $source_values['source_field_label'] = $dealer_plugin->getDestinationMediaTypeSourceFieldLabel();
+      unset($source_values['type']);
+      unset($source_values['scheme']);
+      $rows[$destination_media_type_id] = $source_values;
+    }
+
+    return new \ArrayIterator($rows);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return [
+      'types' => $this->t('File Entity type machine name'),
+      'schemes' => $this->t('The uri scheme of the file entities'),
+      'bundle' => 'bundle',
+      'bundle_label' => 'bundle_label',
+      'source_plugin_id' => $this->t('The source plugin id of the destination media type'),
+      'source_field_name' => $this->t('The source field name of the destination media type'),
+      'source_field_label' => 'source_field_label',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    $ids['types']['type'] = 'string';
+    $ids['schemes']['type'] = 'string';
+    return $ids;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function count($refresh = FALSE) {
+    return $this->initializeIterator()->count();
+  }
+
+}
diff --git a/src/Plugin/migrate/source/d7/FileEntityFieldFormatter.php b/src/Plugin/migrate/source/d7/FileEntityFieldFormatter.php
new file mode 100644
index 0000000..459d524
--- /dev/null
+++ b/src/Plugin/migrate/source/d7/FileEntityFieldFormatter.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\migrate\source\d7;
+
+use Drupal\migrate\Row;
+
+/**
+ * Drupal 7 media field widget settings source based on source database.
+ *
+ * @MigrateSource(
+ *   id = "d7_file_entity_field_formatter",
+ *   source_module = "file_entity"
+ * )
+ */
+class FileEntityFieldFormatter extends FileEntityConfigSourceBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function initializeIterator() {
+    // Media Migration wants to hide "created", "name", "thumbnail" and "uid"
+    // base fields for the default view mode.
+    // @see \Drupal\media\Entity\Media
+    $iterator = parent::initializeIterator();
+    $rows = [];
+    foreach ($iterator->getArrayCopy() as $item) {
+      [
+        'source_field_name' => $source_field_name,
+      ] = $item;
+
+      $field_names = [
+        $source_field_name => FALSE,
+        'created' => TRUE,
+        'name' => TRUE,
+        'thumbnail' => TRUE,
+        'uid' => TRUE,
+      ];
+
+      foreach ($field_names as $field_name => $hidden) {
+        $rows[] = [
+          'field_name' => $field_name,
+          'hidden' => $hidden,
+        ] + $item;
+      }
+    }
+
+    return new \ArrayIterator($rows);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareRow(Row $row) {
+    [
+      'types' => $types,
+      'schemes' => $schemes,
+      'source_field_name' => $source_field_name,
+      'field_name' => $field_name,
+    ] = $row->getSource();
+
+    if ($field_name === $source_field_name) {
+      $type = explode(static::MULTIPLE_SEPARATOR, $types)[0];
+      $scheme = explode(static::MULTIPLE_SEPARATOR, $schemes)[0];
+
+      if (!($dealer_plugin = $this->fileEntityDealerManager->createInstanceFromTypeAndScheme($type, $scheme))) {
+        return FALSE;
+      }
+
+      $dealer_plugin->prepareMediaSourceFieldFormatterRow($row, $this->getDatabase());
+    }
+
+    return parent::prepareRow($row);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return [
+      'field_name' => $this->t('Name of the field.'),
+      'options' => $this->t('Configuration options of the source field widget.'),
+      'hidden' => $this->t('Whether the field is hidden or not.'),
+    ] + parent::fields();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    return [
+      'field_name' => ['type' => 'string'],
+    ] + parent::getIds();
+  }
+
+}
diff --git a/src/Plugin/migrate/source/d7/FileEntityFieldWidget.php b/src/Plugin/migrate/source/d7/FileEntityFieldWidget.php
new file mode 100644
index 0000000..52da1ab
--- /dev/null
+++ b/src/Plugin/migrate/source/d7/FileEntityFieldWidget.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\migrate\source\d7;
+
+use Drupal\migrate\Row;
+
+/**
+ * Drupal 7 media field widget settings source based on source database.
+ *
+ * @MigrateSource(
+ *   id = "d7_file_entity_field_widget",
+ *   source_module = "file_entity"
+ * )
+ */
+class FileEntityFieldWidget extends FileEntityConfigSourceBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareRow(Row $row) {
+    [
+      'types' => $types,
+      'schemes' => $schemes,
+    ] = $row->getSource();
+
+    $type = explode(static::MULTIPLE_SEPARATOR, $types)[0];
+    $scheme = explode(static::MULTIPLE_SEPARATOR, $schemes)[0];
+
+    if (!($dealer_plugin = $this->fileEntityDealerManager->createInstanceFromTypeAndScheme($type, $scheme))) {
+      return FALSE;
+    }
+
+    $dealer_plugin->prepareMediaSourceFieldWidgetRow($row, $this->getDatabase());
+
+    return parent::prepareRow($row);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return [
+      'options' => $this->t('Configuration options of the source field widget.'),
+    ] + parent::fields();
+  }
+
+}
diff --git a/src/Plugin/migrate/source/d7/FileEntityItem.php b/src/Plugin/migrate/source/d7/FileEntityItem.php
index f75af76..3d341a0 100644
--- a/src/Plugin/migrate/source/d7/FileEntityItem.php
+++ b/src/Plugin/migrate/source/d7/FileEntityItem.php
@@ -2,8 +2,13 @@
 
 namespace Drupal\media_migration\Plugin\migrate\source\d7;
 
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\State\StateInterface;
+use Drupal\media_migration\FileEntityDealerManagerInterface;
+use Drupal\migrate\Plugin\MigrationInterface;
 use Drupal\migrate\Row;
 use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * File Entity Item source plugin.
@@ -19,85 +24,125 @@ use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
  */
 class FileEntityItem extends FieldableEntity {
 
-  use MediaFieldQueryTrait;
+  use MediaMigrationDatabaseTrait;
+
+  /**
+   * The file entity dealer plugin manager.
+   *
+   * @var \Drupal\media_migration\FileEntityDealerManagerInterface
+   */
+  protected $fileEntityDealerManager;
+
+  /**
+   * Constructs a FileEntityItem instance.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\migrate\Plugin\MigrationInterface $migration
+   *   The current migration.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\media_migration\FileEntityDealerManagerInterface $file_entity_dealer_manager
+   *   The file entity dealer plugin manager.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager, FileEntityDealerManagerInterface $file_entity_dealer_manager) {
+    $configuration += [
+      'type' => NULL,
+      'scheme' => $configuration['uri_prefix'] ?? NULL,
+    ];
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager);
+    $this->fileEntityDealerManager = $file_entity_dealer_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $migration,
+      $container->get('state'),
+      $container->get('entity_type.manager'),
+      $container->get('plugin.manager.file_entity_dealer')
+    );
+  }
 
   /**
    * {@inheritdoc}
    */
   public function query() {
-    $query = $this->select('file_managed', 'f')
-      ->fields('f')
-      ->orderBy('f.timestamp');
+    [
+      'type' => $type,
+      'scheme' => $scheme,
+    ] = $this->configuration;
 
-    // Filter by type, if configured.
-    if (isset($this->configuration['type'])) {
-      $op = is_array($this->configuration['type']) ? 'IN' : '=';
-      $query->condition('type', $this->configuration['type'], $op);
+    if ($scheme && ($pos = strpos($scheme, '://') !== FALSE)) {
+      $scheme = substr($scheme, 0, $pos);
     }
+    $query = $this->getFileEntityBaseQuery(NULL, FALSE)
+      ->fields('fm')
+      ->orderBy('fm.timestamp');
 
-    // Filter by URI prefix if specified. Default to 'public://'.
-    if (isset($this->configuration['uri_prefix'])) {
-      $query->condition('uri', $this->configuration['uri_prefix'] . '%', 'LIKE');
+    // Filter by type, if configured.
+    if ($type) {
+      $query->condition('fm.type', $type);
     }
-    else {
-      $query->condition('uri', 'public://%', 'LIKE');
+
+    // Filter by URI prefix if specified.
+    if ($scheme) {
+      $query->where("{$this->getSchemeExpression()} = :scheme", [
+        ':scheme' => $scheme,
+      ]);
     }
 
     return $query;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function prepareQuery() {
+    parent::prepareQuery();
+
+    $this->query->addTag('migrate__media_migration');
+    $this->query->addTag('migrate__media_migration__file_entity');
+    $this->query->addTag('migrate__media_migration__media_content');
+    $this->query->addTag("migrate__media_migration__source__{$this->pluginId}");
+
+    return $this->query;
+  }
+
   /**
    * {@inheritdoc}
    */
   public function prepareRow(Row $row) {
-    $media_bundle = $row->getSourceProperty('type');
-    // Get Field API field values.
-    foreach (array_keys($this->getFields('file', $media_bundle)) as $field) {
-      $fid = $row->getSourceProperty('fid');
-      $row->setSourceProperty($field, $this->getFieldValues('file', $field, $fid));
+    [
+      'type' => $type,
+      'scheme' => $scheme,
+    ] = $row->getSource();
+
+    if (!($dealer_plugin = $this->fileEntityDealerManager->createInstanceFromTypeAndScheme($type, $scheme))) {
+      return FALSE;
     }
 
-    if ($media_bundle === 'image') {
-      // Add width and height source properties for image entities. These
-      // properties only exist when the file_entity module is installed on the
-      // source site.
-      $width_and_height_statement = $this->getDatabase()->select('file_metadata', 'fm')
-        ->fields('fm', ['name', 'value'])
-        ->condition('fm.fid', $row->getSourceProperty('fid'))
-        ->condition('fm.name', ['width', 'height'], 'IN')
-        ->execute();
-      foreach ($width_and_height_statement as $result_row) {
-        $row->setSourceProperty($result_row->name, unserialize($result_row->value));
-      }
-
-      // Add alt and title properties from image type fields where the current
-      // image is the file value.
-      $image_field_names = $this->getImageFieldNames();
-
-      $alt_from_field_content = NULL;
-      $title_from_field_content = NULL;
-      foreach ($image_field_names as $field_name) {
-        $alt_and_title_from_field_content_query = $this->getDatabase()->select("field_data_$field_name", $field_name);
-        $alt_and_title_from_field_content_query->addField($field_name, "{$field_name}_alt", 'alt');
-        $alt_and_title_from_field_content_query->addField($field_name, "{$field_name}_title", 'title');
-        $alt_and_title_from_field_content_query->condition("$field_name.{$field_name}_fid", $row->getSourceProperty('fid'));
-        $alt_and_title_from_field_content_query->orderBy("$field_name.revision_id", 'DESC');
-        $alt_and_title_from_field_content = $alt_and_title_from_field_content_query->execute()->fetchAll();
-
-        if (count($alt_and_title_from_field_content) > 0) {
-          // We may find more than one alt-title props in other fields – we use
-          // the first row.
-          if (!$alt_from_field_content && !$title_from_field_content) {
-            $alt_from_field_content = $alt_from_field_content ?? $alt_and_title_from_field_content[0]->alt;
-            $title_from_field_content = $title_from_field_content ?? $alt_and_title_from_field_content[0]->title;
-          }
-        }
-      }
-
-      $row->setSourceProperty('image_field_alt', $alt_from_field_content);
-      $row->setSourceProperty('image_field_title', $title_from_field_content);
+    // Get Field API field values.
+    $fields = $this->getFields('file', $type);
+    $file_id = $row->getSourceProperty('fid');
+    foreach (array_keys($fields) as $field_name) {
+      $row->setSourceProperty($field_name, $this->getFieldValues('file', $field_name, $file_id));
     }
 
+    $row->setSourceProperty('bundle', $dealer_plugin->getDestinationMediaTypeId());
+    $dealer_plugin->prepareMediaEntityRow($row, $this->getDatabase());
+
     return parent::prepareRow($row);
   }
 
@@ -123,6 +168,8 @@ class FileEntityItem extends FieldableEntity {
       'promote' => $this->t('The promoted flag - (if file_admin module is present in Drupal 7)'),
       'sticky' => $this->t('The sticky flag - (if file_admin module is present in Drupal 7)'),
       'vid' => $this->t('The vid'),
+      'image_field_alt' => $this->t('The alternate text for the image (if this is a value of an image field)'),
+      'image_field_text' => $this->t('The title text for the image (if this is a value of an image field)'),
     ];
 
     return $fields;
diff --git a/src/Plugin/migrate/source/d7/FileEntitySourceFieldInstance.php b/src/Plugin/migrate/source/d7/FileEntitySourceFieldInstance.php
new file mode 100644
index 0000000..616dfde
--- /dev/null
+++ b/src/Plugin/migrate/source/d7/FileEntitySourceFieldInstance.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\migrate\source\d7;
+
+use Drupal\migrate\Row;
+
+/**
+ * Drupal 7 media source field instances source based on source database.
+ *
+ * @MigrateSource(
+ *   id = "d7_file_entity_source_field_instance",
+ *   source_module = "file_entity"
+ * )
+ */
+class FileEntitySourceFieldInstance extends FileEntityConfigSourceBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    $query = parent::query();
+    $query->addExpression($this->getExtensionExpression(), 'file_extension');
+
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function initializeIterator() {
+    $results = $this->prepareQuery()->execute()->fetchAll();
+    $rows = [];
+    foreach ($results as $result) {
+      [
+        'type' => $type,
+        'scheme' => $scheme,
+        'file_extension' => $extension,
+      ] = $result;
+
+      if (!($dealer_plugin = $this->fileEntityDealerManager->createInstanceFromTypeAndScheme($type, $scheme))) {
+        continue;
+      }
+      $destination_media_type_id = $dealer_plugin->getDestinationMediaTypeId();
+      $source_values = $rows[$destination_media_type_id] ?? $result + [
+        'types' => $type,
+        'schemes' => $scheme,
+        'file_extensions' => $extension,
+      ];
+
+      $source_values['types'] = implode(self::MULTIPLE_SEPARATOR, array_unique(array_merge(explode(self::MULTIPLE_SEPARATOR, $source_values['types']), [$type])));
+      $source_values['schemes'] = implode(self::MULTIPLE_SEPARATOR, array_unique(array_merge(explode(self::MULTIPLE_SEPARATOR, $source_values['schemes']), [$scheme])));
+      $source_values['file_extensions'] = implode(self::MULTIPLE_SEPARATOR, array_unique(array_merge(explode(self::MULTIPLE_SEPARATOR, $source_values['file_extensions']), [$extension])));
+      $source_values['bundle'] = $destination_media_type_id;
+      $source_values['bundle_label'] = $dealer_plugin->getDestinationMediaTypeLabel();
+      $source_values['source_plugin_id'] = $dealer_plugin->getDestinationMediaSourcePluginId();
+      $source_values['source_field_name'] = $dealer_plugin->getDestinationMediaSourceFieldName();
+      $source_values['source_field_label'] = $dealer_plugin->getDestinationMediaTypeSourceFieldLabel();
+      unset($source_values['type']);
+      unset($source_values['scheme']);
+      unset($source_values['extension']);
+      $rows[$destination_media_type_id] = $source_values;
+    }
+
+    return new \ArrayIterator($rows);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return [
+      'file_extensions' => $this->t('The allowed extensions of a media source field which base type is file, separated by "::"'),
+    ] + parent::fields();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    $ids['types']['type'] = 'string';
+    $ids['schemes']['type'] = 'string';
+    return $ids;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareRow(Row $row) {
+    [
+      'types' => $types,
+      'schemes' => $schemes,
+    ] = $row->getSource();
+
+    $type = explode(static::MULTIPLE_SEPARATOR, $types)[0];
+    $scheme = explode(static::MULTIPLE_SEPARATOR, $schemes)[0];
+
+    if (!($dealer_plugin = $this->fileEntityDealerManager->createInstanceFromTypeAndScheme($type, $scheme))) {
+      return FALSE;
+    }
+
+    $dealer_plugin->prepareMediaSourceFieldInstanceRow($row, $this->getDatabase());
+
+    return parent::prepareRow($row);
+  }
+
+}
diff --git a/src/Plugin/migrate/source/d7/FileEntitySourceFieldStorage.php b/src/Plugin/migrate/source/d7/FileEntitySourceFieldStorage.php
new file mode 100644
index 0000000..dee8c5d
--- /dev/null
+++ b/src/Plugin/migrate/source/d7/FileEntitySourceFieldStorage.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\media_migration\Plugin\migrate\source\d7;
+
+use Drupal\migrate\Row;
+
+/**
+ * Drupal 7 media source field storage source based on source database.
+ *
+ * @MigrateSource(
+ *   id = "d7_file_entity_source_field_storage",
+ *   source_module = "file_entity"
+ * )
+ */
+class FileEntitySourceFieldStorage extends FileEntityConfigSourceBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareRow(Row $row) {
+    [
+      'types' => $types,
+      'schemes' => $schemes,
+    ] = $row->getSource();
+
+    $type = explode(static::MULTIPLE_SEPARATOR, $types)[0];
+    $scheme = explode(static::MULTIPLE_SEPARATOR, $schemes)[0];
+
+    if (!($dealer_plugin = $this->fileEntityDealerManager->createInstanceFromTypeAndScheme($type, $scheme))) {
+      return FALSE;
+    }
+
+    $dealer_plugin->prepareMediaSourceFieldStorageRow($row, $this->getDatabase());
+
+    return parent::prepareRow($row);
+  }
+
+}
diff --git a/src/Plugin/migrate/source/d7/FileEntityType.php b/src/Plugin/migrate/source/d7/FileEntityType.php
index b0ccddc..1ce67b1 100644
--- a/src/Plugin/migrate/source/d7/FileEntityType.php
+++ b/src/Plugin/migrate/source/d7/FileEntityType.php
@@ -3,7 +3,6 @@
 namespace Drupal\media_migration\Plugin\migrate\source\d7;
 
 use Drupal\migrate\Row;
-use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
 
 /**
  * File Entity Type source plugin.
@@ -13,66 +12,26 @@ use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
  *   source_module = "file_entity"
  * )
  */
-class FileEntityType extends DrupalSqlBase {
+class FileEntityType extends FileEntityConfigSourceBase {
 
   /**
    * {@inheritdoc}
    */
-  public function query() {
-    // 'undefined' file type refers to files that file_entity could not
-    // classify. Probably because there was an error during
-    // its creation.
-    $query = $this->select('file_managed', 'fm')
-      ->distinct()
-      ->fields('fm', ['type'])
-      ->condition('fm.status', TRUE)
-      ->condition('uri', 'public://%', 'LIKE');
-
-    // Filter by type if available.
-    if (isset($this->configuration['type'])) {
-      if (!is_string($this->configuration['type'])) {
-        throw new \LogicException('File entity source type must be the ID of a source file entity bundle');
-      }
-      $query->condition('fm.type', $this->configuration['type']);
-    }
-    else {
-      $query->condition('fm.type', 'undefined', '<>');
-    }
-
-    return $query;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fields() {
-    return [
-      'type' => $this->t('File Entity type machine name'),
-    ];
-  }
+  public function prepareRow(Row $row) {
+    [
+      'types' => $types,
+      'schemes' => $schemes,
+    ] = $row->getSource();
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getIds() {
-    $ids['type']['type'] = 'string';
-    return $ids;
-  }
+    $type = explode(static::MULTIPLE_SEPARATOR, $types)[0];
+    $scheme = explode(static::MULTIPLE_SEPARATOR, $schemes)[0];
 
-  /**
-   * {@inheritdoc}
-   */
-  public function prepareRow(Row $row) {
-    // Don't migrate bundles which don't exist in the destination.
-    $type = $row->getSourceProperty('type');
-    // @todo Remove checking of $this->entityManager and
-    //   $this->entityTypeManager and leave only $this->entityManager when
-    //   Drupal core 8.7.x security support ends.
-    $manager = $this->entityTypeManager ?? $this->entityManager ?? NULL;
-    if (!$manager || !$manager->getStorage('media_type')->load($type)) {
+    if (!($dealer_plugin = $this->fileEntityDealerManager->createInstanceFromTypeAndScheme($type, $scheme))) {
       return FALSE;
     }
 
+    $dealer_plugin->prepareMediaTypeRow($row, $this->getDatabase());
+
     return parent::prepareRow($row);
   }
 
diff --git a/src/Plugin/migrate/source/d7/FilePlain.php b/src/Plugin/migrate/source/d7/FilePlain.php
index 9cc2dac..8b3a88b 100644
--- a/src/Plugin/migrate/source/d7/FilePlain.php
+++ b/src/Plugin/migrate/source/d7/FilePlain.php
@@ -90,7 +90,7 @@ class FilePlain extends FieldableEntity implements ContainerFactoryPluginInterfa
       'scheme' => $scheme,
     ] = $this->configuration;
 
-    $query = $this->getBaseQuery(NULL, FALSE);
+    $query = $this->getFilePlainBaseQuery(NULL, FALSE);
     $query->fields('fm');
     $query->orderBy('fm.timestamp', 'ASC');
 
diff --git a/src/Plugin/migrate/source/d7/FilePlainConfigSourceBase.php b/src/Plugin/migrate/source/d7/FilePlainConfigSourceBase.php
index 12a779b..9caa8f1 100644
--- a/src/Plugin/migrate/source/d7/FilePlainConfigSourceBase.php
+++ b/src/Plugin/migrate/source/d7/FilePlainConfigSourceBase.php
@@ -83,7 +83,7 @@ abstract class FilePlainConfigSourceBase extends DrupalSqlBase {
       'mimes' => $mimes,
       'schemes' => $schemes,
     ] = $this->configuration;
-    $query = $this->getBaseQuery();
+    $query = $this->getFilePlainBaseQuery();
 
     if ($this->sourceHasFileEntities) {
       $query->condition('fm.type', ['undefined', ''], 'IN');
diff --git a/src/Plugin/migrate/source/d7/MediaMigrationDatabaseTrait.php b/src/Plugin/migrate/source/d7/MediaMigrationDatabaseTrait.php
index cfb4ad4..b89a16b 100644
--- a/src/Plugin/migrate/source/d7/MediaMigrationDatabaseTrait.php
+++ b/src/Plugin/migrate/source/d7/MediaMigrationDatabaseTrait.php
@@ -11,7 +11,7 @@ use Drupal\Core\Database\Driver\sqlite\Connection as SqLiteConnection;
 trait MediaMigrationDatabaseTrait {
 
   /**
-   * Returns a base query for file entity types.
+   * Returns a base query for plain files.
    *
    * @param \Drupal\Core\Database\Connection|null $connection
    *   Database connection of the source Drupal 7 instance.
@@ -21,7 +21,7 @@ trait MediaMigrationDatabaseTrait {
    * @return \Drupal\Core\Database\Query\SelectInterface
    *   The base query.
    */
-  protected function getBaseQuery($connection = NULL, bool $distinct = TRUE) {
+  protected function getFilePlainBaseQuery($connection = NULL, bool $distinct = TRUE) {
     $db = $connection ?? $this->getDatabase();
     assert($db instanceof Connection);
     $options = [
@@ -37,6 +37,36 @@ trait MediaMigrationDatabaseTrait {
     return $query;
   }
 
+  /**
+   * Returns a base query for file entity types.
+   *
+   * @param \Drupal\Core\Database\Connection|null $connection
+   *   Database connection of the source Drupal 7 instance.
+   * @param bool $distinct
+   *   Base query should use distinct.
+   *
+   * @return \Drupal\Core\Database\Query\SelectInterface
+   *   The base query.
+   */
+  protected function getFileEntityBaseQuery($connection = NULL, bool $distinct = TRUE) {
+    $db = $connection ?? $this->getDatabase();
+    assert($db instanceof Connection);
+    $options = [
+      'fetch' => \PDO::FETCH_ASSOC,
+    ];
+    $query = $db->select('file_managed', 'fm', $options);
+    if ($distinct) {
+      $query->distinct();
+    }
+    $query->fields('fm', ['type'])
+      ->condition('fm.status', TRUE)
+      ->condition('fm.uri', 'temporary://%', 'NOT LIKE')
+      ->condition('fm.type', 'undefined', '<>');
+    $query->addExpression($this->getSchemeExpression($db), 'scheme');
+
+    return $query;
+  }
+
   /**
    * Returns the expression for the DB for getting the URI scheme.
    *
diff --git a/src/Plugin/migrate/source/d7/MediaSourceFieldInstance.php b/src/Plugin/migrate/source/d7/MediaSourceFieldInstance.php
deleted file mode 100644
index e0c8c40..0000000
--- a/src/Plugin/migrate/source/d7/MediaSourceFieldInstance.php
+++ /dev/null
@@ -1,398 +0,0 @@
-<?php
-
-namespace Drupal\media_migration\Plugin\migrate\source\d7;
-
-use Drupal\Core\Database\Driver\sqlite\Connection as SqLiteConnection;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\State\StateInterface;
-use Drupal\Core\StreamWrapper\StreamWrapperInterface;
-use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
-use Drupal\media\MediaTypeInterface;
-use Drupal\media\Plugin\media\Source\File;
-use Drupal\media\Plugin\media\Source\Image;
-use Drupal\migrate\Plugin\MigrationInterface;
-use Drupal\migrate\Row;
-use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Drupal 7 media source field instances source based on source database.
- *
- * @MigrateSource(
- *   id = "d7_media_source_field_instance",
- *   source_module = "file_entity"
- * )
- */
-class MediaSourceFieldInstance extends DrupalSqlBase {
-
-  use MediaFieldQueryTrait;
-
-  /**
-   * The map of the alt and title properties and their corresponding field name.
-   *
-   * These property fields only exist when the file_entity module is installed
-   * on the source site.
-   *
-   * @var string[]
-   */
-  protected $fieldNamePropertyMap = [
-    'field_file_image_alt_text' => 'alt_field',
-    'field_file_image_title_text' => 'title_field',
-  ];
-
-  /**
-   * The list of the visible, writable and readable streams.
-   *
-   * @var string[]
-   */
-  protected $publicStreams;
-
-  /**
-   * The media type's storage.
-   *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
-   */
-  protected $mediaTypeStorage;
-
-  /**
-   * Constructs a MediaSourceFieldInstance instance.
-   *
-   * @param array $configuration
-   *   A configuration array containing information about the plugin instance.
-   * @param string $plugin_id
-   *   The plugin_id for the plugin instance.
-   * @param mixed $plugin_definition
-   *   The plugin implementation definition.
-   * @param \Drupal\migrate\Plugin\MigrationInterface $migration
-   *   The current migration.
-   * @param \Drupal\Core\State\StateInterface $state
-   *   The state service.
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   *   The entity type manager.
-   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
-   *   The stream wrapper manager.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
-   *   Thrown when the media_type entity type doesn't exist.
-   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
-   *   Thrown when the media_type storage handler couldn't be loaded.
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager, StreamWrapperManagerInterface $stream_wrapper_manager) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager);
-    // For the file migration, we need writable streams. This also means that
-    // these streams are readable as well. On the other hand, only visible
-    // streams can be configured for file|image fields on Drupal 8|9, and
-    // Media Migration wants to respect this decision.
-    // @see \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface::WRITE_VISIBLE
-    $this->publicStreams = array_keys($stream_wrapper_manager->getNames(StreamWrapperInterface::WRITE_VISIBLE));
-    $this->mediaTypeStorage = $entity_type_manager->getStorage('media_type');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
-    return new static(
-      $configuration,
-      $plugin_id,
-      $plugin_definition,
-      $migration,
-      $container->get('state'),
-      $container->get('entity_type.manager'),
-      $container->get('stream_wrapper_manager')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function query() {
-    $scheme_expression = $this->getDatabase() instanceof SqLiteConnection
-      ? "SUBSTRING(msfc.uri, 1, INSTR(msfc.uri, '://') - 1)"
-      : "SUBSTRING(msfc.uri, 1, POSITION('://' IN msfc.uri) - 1)";
-    $query = $this->select('file_managed', 'msfc')
-      ->distinct()
-      ->condition('msfc.status', TRUE)
-      ->fields('msfc', ['type']);
-    $query->addExpression($scheme_expression, 'scheme');
-
-    // Always filter by public streams, optionally restrict to a specific one.
-    if (!empty($this->configuration['scheme'])) {
-      $query->where("($scheme_expression = :scheme)", [
-        ':scheme' => $this->configuration['scheme'],
-      ]);
-    }
-    else {
-      $query->where("($scheme_expression IN (:schemes[]))", [
-        ':schemes[]' => $this->publicStreams,
-      ]);
-    }
-
-    // Filter by source type if available.
-    if (!empty($this->configuration['type'])) {
-      if (!is_string($this->configuration['type'])) {
-        throw new \LogicException('File entity source type must be the ID of a source file entity bundle');
-      }
-      $query->condition('msfc.type', $this->configuration['type']);
-    }
-    else {
-      $query->condition('msfc.type', 'undefined', '<>');
-    }
-
-    return $query;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fields() {
-    return [
-      'field_name' => $this->t('The field name of the corresponding media source field.'),
-      'type' => $this->t('The entity bundle.'),
-      'scheme' => $this->t('The scheme the media assets were stored with.'),
-      'file_extensions' => $this->t('The allowed extensions of a media source field which base type is file.'),
-      'alt_field' => $this->t('The alt field of an image source field is enabled.'),
-      'alt_field_required' => $this->t('The alt field of an image source field is required.'),
-      'title_field' => $this->t('The title field of an image source field is enabled.'),
-      'title_field_required' => $this->t('The title field of an image source field is required.'),
-    ];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getIds() {
-    return [
-      'type' => [
-        'type' => 'string',
-        'alias' => 'msfc',
-      ],
-    ];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function prepareRow(Row $row) {
-    [
-      'type' => $source_type,
-      'scheme' => $scheme,
-    ] = $row->getSource();
-
-    if (!$source_type || !$scheme) {
-      return FALSE;
-    }
-
-    // We have to find the media type whose source field's URI scheme matches
-    // the current one.
-    $media_type = $this->mediaTypeStorage->load($source_type);
-
-    if ($media_type === NULL) {
-      return FALSE;
-    }
-
-    assert($media_type instanceof MediaTypeInterface);
-    $media_source = $media_type->getSource();
-    $media_source_field_config_def = $media_source->getSourceFieldDefinition($media_type);
-    $media_source_field_storage_def = $media_source_field_config_def->getFieldStorageDefinition();
-    $additional_properties = [
-      'field_name' => $media_source->getConfiguration()['source_field'],
-    ];
-
-    // If the media source plugin is an instance of the File plugin, that means
-    // that the corresponding field configuration has an allowed file extensions
-    // configuration. This is true for even AudioFile, VideoFile and Image.
-    if ($media_source instanceof File) {
-      assert($media_source_field_storage_def->getSetting('uri_scheme') === $scheme);
-      // The field storage's URI scheme has to match the current scheme when
-      // we have only one scheme.
-      // @todo handle "max_filesize" as well.
-      $additional_properties['file_extensions'] = $media_source_field_config_def->getSettings()['file_extensions'] ?? '';
-      $scheme_expression = $this->getDatabase() instanceof SqLiteConnection
-        ? "SUBSTRING(fmext.uri, 1, INSTR(fmext.uri, '://') - 1)"
-        : "SUBSTRING(fmext.uri, 1, POSITION('://' IN fmext.uri) - 1)";
-
-      // We have to collect every different file extension of the existing
-      // files.
-      $required_extensions_results = $this->getDatabase()->select('file_managed', 'fmext')
-        ->distinct()
-        ->condition('fmext.status', TRUE)
-        ->fields('fmext', ['uri'])
-        ->condition('fmext.type', $source_type)
-        ->where("$scheme_expression = :scheme", [':scheme' => $scheme])
-        ->execute()
-        ->fetchAll();
-      $required_extensions = array_reduce($required_extensions_results, function (array $extensions, $item) {
-        $extension = pathinfo($item->uri)['extension'] ?? NULL;
-        if ($extension && !in_array($extension, $extensions)) {
-          $extensions[] = $extension;
-        }
-        return $extensions;
-      }, []);
-
-      if (!empty($required_extensions)) {
-        $preexisting_extensions = !empty($additional_properties['file_extensions'])
-          ? array_filter(array_unique(explode(' ', $additional_properties['file_extensions'])))
-          : [];
-        $additional_properties['file_extensions'] = implode(' ', array_unique(array_merge($preexisting_extensions, $required_extensions)));
-      }
-    }
-
-    if ($media_source instanceof Image) {
-      // Alt and title properties of an image field always exist, regardless
-      // of whether they are accessible and editable on the entity edit form or
-      // not.
-      // But we also want to make sure that if either the alt or title property
-      // was editable on the source site, then it will be editable also on the
-      // destination site.
-      // For first, we try to determine which properties should be enabled (and
-      // thus accessible and editable on UI) based on the source media image
-      // entities' alt and title fields' content. We assume that if there is at
-      // least a single value for an image's title property in the source
-      // database, then we have to change visibility of the title property to
-      // TRUE, so that it can be edited on the destination site as well.
-      $alt_title_config = $this->getImageAltTitleSettingsFromPropertyFieldContent();
-      // All image field content is also migrated into a media entity. If any of
-      // the alt or title properties shouldn't be necessarily shown (and be
-      // editable) on the entity edit form based on the source media entity's
-      // alt or title field value, we still have to check the configuration of
-      // the source site's image fields.
-      // If any of the preexisting image field was configured to show the alt or
-      // the title property, then we will make their input field visible.
-      if (!empty(array_diff(array_values($this->fieldNamePropertyMap), array_keys($alt_title_config)))) {
-        $alt_title_config += $this->getSettingsFromImageFields();
-      }
-
-      // Get the 'required' settings from the image fields we found.
-      $this->mergePropertyRequiredSettingsFromImageFields($alt_title_config);
-
-      // Add the discovered and the default configuration.
-      $additional_properties = $alt_title_config + [
-        'alt_field' => TRUE,
-        'alt_field_required' => TRUE,
-        'title_field' => FALSE,
-        'title_field_required' => FALSE,
-      ] + $additional_properties;
-    }
-
-    foreach ($additional_properties as $property_name => $property_value) {
-      $row->setSourceProperty($property_name, $property_value);
-    }
-
-    return parent::prepareRow($row);
-  }
-
-  /**
-   * Discovers the image field settings based on existing property values.
-   *
-   * The alt and title properties of an image field always exist, regardless
-   * of whether they are actually accessible and editable on the entity edit
-   * form or not. This method checks the content of the corresponding alt and
-   * title fields. If we find at least a single, non-empty row, we say that the
-   * actual property should be shown on the destination media entity's edit
-   * form.
-   *
-   * @return true[]
-   *   An array of those field instance settings that should be revealed, keyed
-   *   by the settings key ('alt_field', 'title_field').
-   *   For example, if we have rows for alt, but don't have any data for title,
-   *   the array returned will be this:
-   *   @code
-   *   [
-   *     'alt_field' => TRUE
-   *   ]
-   *   @endcode
-   */
-  protected function getImageAltTitleSettingsFromPropertyFieldContent() {
-    $data = [];
-
-    foreach ($this->fieldNamePropertyMap as $field_name => $property) {
-      $property_values_query = $this->getDatabase()->select("field_data_$field_name", $field_name)
-        ->fields($field_name)
-        ->condition("$field_name.{$field_name}_value", '', '<>')
-        ->isNotNull("$field_name.{$field_name}_value");
-      $property_values_present = (int) $property_values_query->countQuery()->execute()->fetchField() > 0;
-
-      if ($property_values_present) {
-        $data[$property] = TRUE;
-      }
-    }
-
-    return $data;
-  }
-
-  /**
-   * Discovers enabled properties based on the image field configurations.
-   *
-   * The alt and title properties of an image field always exist, regardless
-   * of that they are actually accessible and editable on the entity edit form.
-   * This method checks the config of every image field's instance configuration
-   * When alt (or title) was enabled for at least one image field, we say that
-   * the property should be shown on the destination media entity's edit
-   * form.
-   *
-   * @return array
-   *   An array of those settings that should be revealed, keyed by the settings
-   *   key ('alt_field', 'title_field').
-   */
-  protected function getSettingsFromImageFields() {
-    $data = [];
-    $image_field_names = $this->getImageFieldNames();
-
-    foreach ($image_field_names as $image_field_name) {
-      $field_instance_config_results = $this->getDatabase()->select('field_config_instance', 'fci')
-        ->fields('fci', ['data'])
-        ->condition('fci.field_name', $image_field_name)
-        ->condition('fci.entity_type', 'file', '<>')
-        ->execute()
-        ->fetchAll();
-
-      foreach ($field_instance_config_results as $field_instance_config_result) {
-        $field_config_data = unserialize($field_instance_config_result->data);
-
-        foreach ($this->fieldNamePropertyMap as $property) {
-          if (isset($field_config_data['settings'][$property]) && !empty($field_config_data['settings'][$property])) {
-            $data[$property] = TRUE;
-          }
-        }
-
-        if (empty(array_diff(array_values($this->fieldNamePropertyMap), array_keys($data)))) {
-          break 2;
-        }
-      }
-    }
-
-    return $data;
-  }
-
-  /**
-   * Merges alt/title 'required' settings based on image field discovery.
-   *
-   * If any image field have alt (or title) set up as optional, we don't let
-   * them being required.
-   *
-   * @param array $data
-   *   The discovered data about alt and title revealment.
-   */
-  protected function mergePropertyRequiredSettingsFromImageFields(array &$data) {
-    foreach ($this->fieldNamePropertyMap as $field_name => $property) {
-      $property_field_config_result = $this->getDatabase()->select('field_config_instance', 'fci')
-        ->fields('fci', ['data'])
-        ->condition('fci.field_name', $field_name)
-        ->condition('fci.bundle', 'image')
-        ->condition('fci.entity_type', 'file')
-        ->execute()
-        ->fetchAll();
-
-      assert(count($property_field_config_result) === 1);
-
-      if ($property_field_config_data = unserialize($property_field_config_result[0]->data)) {
-        if (isset($property_field_config_data['required'])) {
-          $not_set_or_not_required = !isset($data["{$property}_required"]) || empty($data["{$property}_required"]);
-          $data["{$property}_required"] = $not_set_or_not_required && !empty($data[$property]) && !empty($property_field_config_data['required']);
-        }
-      }
-    }
-  }
-
-}
diff --git a/src/Plugin/migrate/source/d7/MediaViewMode.php b/src/Plugin/migrate/source/d7/MediaViewMode.php
index 29e3fe4..0d4f803 100644
--- a/src/Plugin/migrate/source/d7/MediaViewMode.php
+++ b/src/Plugin/migrate/source/d7/MediaViewMode.php
@@ -27,42 +27,41 @@ class MediaViewMode extends DrupalSqlBase {
    * {@inheritdoc}
    */
   protected function initializeIterator() {
-    $rows = [];
-    if ($this->moduleExists('file_entity')) {
-      $rows[] = [
+    $rows = [
+      [
         'mode' => 'full',
         'label' => $this->getMediaViewModeLabel('full'),
-      ];
-      $rows[] = [
+      ],
+      [
         'mode' => 'preview',
         'label' => $this->getMediaViewModeLabel('preview'),
-      ];
-      $rows[] = [
+      ],
+      [
         'mode' => 'rss',
         'label' => $this->getMediaViewModeLabel('rss'),
-      ];
-      $rows[] = [
+      ],
+      [
         'mode' => 'teaser',
         'label' => $this->getMediaViewModeLabel('teaser'),
-      ];
+      ],
+    ];
 
-      if ($this->moduleExists('search')) {
-        $rows[] = [
-          'mode' => 'search_index',
-          'label' => $this->getMediaViewModeLabel('search_index'),
-        ];
-        $rows[] = [
-          'mode' => 'search_result',
-          'label' => $this->getMediaViewModeLabel('search_result'),
-        ];
-      }
+    if ($this->moduleExists('search')) {
+      $rows[] = [
+        'mode' => 'search_index',
+        'label' => $this->getMediaViewModeLabel('search_index'),
+      ];
+      $rows[] = [
+        'mode' => 'search_result',
+        'label' => $this->getMediaViewModeLabel('search_result'),
+      ];
+    }
 
-      if ($this->moduleExists('media_wysiwyg')) {
-        $rows[] = [
-          'mode' => 'wysiwyg',
-          'label' => $this->getMediaViewModeLabel('wysiwyg'),
-        ];
-      }
+    if ($this->moduleExists('media_wysiwyg')) {
+      $rows[] = [
+        'mode' => 'wysiwyg',
+        'label' => $this->getMediaViewModeLabel('wysiwyg'),
+      ];
     }
 
     return new \ArrayIterator($rows);
diff --git a/tests/modules/media_migration_test_dealer_plugins/media_migration_test_dealer_plugins.info.yml b/tests/modules/media_migration_test_dealer_plugins/media_migration_test_dealer_plugins.info.yml
new file mode 100644
index 0000000..f3e532b
--- /dev/null
+++ b/tests/modules/media_migration_test_dealer_plugins/media_migration_test_dealer_plugins.info.yml
@@ -0,0 +1,6 @@
+name: Media Migration simple dealer plugin replacements
+type: module
+description: 'Provides simplified plugin replacements.'
+package: Testing
+dependencies:
+  - drupal:media_migration
diff --git a/tests/modules/media_migration_test_dealer_plugins/media_migration_test_dealer_plugins.module b/tests/modules/media_migration_test_dealer_plugins/media_migration_test_dealer_plugins.module
new file mode 100644
index 0000000..694469a
--- /dev/null
+++ b/tests/modules/media_migration_test_dealer_plugins/media_migration_test_dealer_plugins.module
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Test helper module for providing simplified dealer plugins.
+ */
+
+use Drupal\media_migration_test_dealer_plugins\Image;
+
+/**
+ * Implements hook_media_migration_file_entity_dealer_info_alter().
+ */
+function media_migration_test_dealer_plugins_media_migration_file_entity_dealer_info_alter(&$definitions) {
+  if (!empty($definitions['image'])) {
+    $definitions['image']['class'] = Image::class;
+  }
+}
diff --git a/tests/modules/media_migration_test_dealer_plugins/src/Image.php b/tests/modules/media_migration_test_dealer_plugins/src/Image.php
new file mode 100644
index 0000000..8b96074
--- /dev/null
+++ b/tests/modules/media_migration_test_dealer_plugins/src/Image.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\media_migration_test_dealer_plugins;
+
+use Drupal\media_migration\Plugin\media_migration\file_entity\FileBase;
+
+/**
+ * Simple image media migration plugin for local image media entities.
+ */
+class Image extends FileBase {
+
+}
diff --git a/tests/modules/media_migration_test_oembed/media_migration_test_oembed.info.yml b/tests/modules/media_migration_test_oembed/media_migration_test_oembed.info.yml
new file mode 100644
index 0000000..43e379d
--- /dev/null
+++ b/tests/modules/media_migration_test_oembed/media_migration_test_oembed.info.yml
@@ -0,0 +1,6 @@
+name: Media Migration oEmbed Test Helper
+type: module
+description: 'Provides functionality to suppress oEmbed related outbound requests.'
+package: Testing
+dependencies:
+  - drupal:media
diff --git a/tests/modules/media_migration_test_oembed/src/MediaMigrationTestOembedServiceProvider.php b/tests/modules/media_migration_test_oembed/src/MediaMigrationTestOembedServiceProvider.php
new file mode 100644
index 0000000..9fc4cf3
--- /dev/null
+++ b/tests/modules/media_migration_test_oembed/src/MediaMigrationTestOembedServiceProvider.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\media_migration_test_oembed;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DependencyInjection\ServiceProviderBase;
+
+/**
+ * Replaces oEmbed-related media services which would make outbound requests.
+ */
+class MediaMigrationTestOembedServiceProvider extends ServiceProviderBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alter(ContainerBuilder $container) {
+    $container->getDefinition('media.oembed.url_resolver')->setClass(UrlResolver::class);
+    $container->getDefinition('media.oembed.resource_fetcher')->setClass(ResourceFetcher::class);
+  }
+
+}
diff --git a/tests/modules/media_migration_test_oembed/src/ResourceFetcher.php b/tests/modules/media_migration_test_oembed/src/ResourceFetcher.php
new file mode 100644
index 0000000..53b21fd
--- /dev/null
+++ b/tests/modules/media_migration_test_oembed/src/ResourceFetcher.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\media_migration_test_oembed;
+
+use Drupal\Component\Utility\Crypt;
+use Drupal\media\OEmbed\ResourceException;
+use Drupal\media\OEmbed\ResourceFetcher as BaseResourceFetcher;
+use Drupal\media\OEmbed\Resource;
+
+/**
+ * Simple oEmbed resource fetcher replacement for remote media migration tests.
+ */
+class ResourceFetcher extends BaseResourceFetcher {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchResource($oembed_url) {
+    $hashed = Crypt::hashBase64($oembed_url);
+    $resource_array = \Drupal::state()->get("media_migration_test_oembed.$hashed", [
+      'type' => 'video',
+      'html' => urlencode($oembed_url),
+      'width' => 320,
+      'height' => 180,
+    ]);
+    $type = $resource_array['type'];
+    unset($resource_array['type']);
+    try {
+      $resource = call_user_func_array([Resource::class, $type], $resource_array);
+    }
+    catch (\Exception $e) {
+      throw new ResourceException(sprintf('Test media oembed resource %s cannot be fetched', $oembed_url), $oembed_url);
+    }
+    assert($resource instanceof Resource);
+    return $resource;
+  }
+
+}
diff --git a/tests/modules/media_migration_test_oembed/src/UrlResolver.php b/tests/modules/media_migration_test_oembed/src/UrlResolver.php
new file mode 100644
index 0000000..13e80b1
--- /dev/null
+++ b/tests/modules/media_migration_test_oembed/src/UrlResolver.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Drupal\media_migration_test_oembed;
+
+use Drupal\media\OEmbed\UrlResolver as BaseUrlResolver;
+
+/**
+ * Simple oEmbed URL resolver replacement for remote media migration tests.
+ */
+class UrlResolver extends BaseUrlResolver {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getResourceUrl($url, $max_width = NULL, $max_height = NULL) {
+    return $url;
+  }
+
+}
diff --git a/tests/src/Functional/MigrateEmbedMediaTokenToEntityEmbedTest.php b/tests/src/Functional/MigrateEmbedMediaTokenToEntityEmbedTest.php
index 31f50ee..df054fe 100644
--- a/tests/src/Functional/MigrateEmbedMediaTokenToEntityEmbedTest.php
+++ b/tests/src/Functional/MigrateEmbedMediaTokenToEntityEmbedTest.php
@@ -43,10 +43,24 @@ class MigrateEmbedMediaTokenToEntityEmbedTest extends MigrateEmbedMediaTokenTest
    * @param string|bool[][] $expected_embed_html_properties
    *   The expected attributes of the embed entity HTML tags, keyed by their
    *   delta (from their order in node with ID '1').
+   * @param bool $preexisting_media_types
+   *   Whether media types should be present before the migration.
    *
    * @dataProvider providerEntityEmbedTransform
    */
-  public function testMediaTokenToEntityEmbedTransform(string $reference_method, $destination_format_plugin_id, array $expected_embed_html_properties) {
+  public function testMediaTokenToEntityEmbedTransform(string $reference_method, $destination_format_plugin_id, array $expected_embed_html_properties, bool $preexisting_media_types) {
+    // Delete preexisting media types.
+    $media_types = $this->container->get('entity_type.manager')
+      ->getStorage('media_type')
+      ->loadMultiple();
+    foreach ($media_types as $media_type) {
+      $media_type->delete();
+    }
+
+    if ($preexisting_media_types) {
+      $this->createStandardMediaTypes(TRUE);
+    }
+
     // Set the reference method.
     $this->setEmbedMediaReferenceMethod($reference_method);
 
@@ -88,36 +102,52 @@ class MigrateEmbedMediaTokenToEntityEmbedTest extends MigrateEmbedMediaTokenTest
       'data-align' => 'center',
     ];
 
-    return [
-      '"ID" reference method, non-defined destination format plugin ID' => [
+    $test_cases = [
+      '"ID" reference method, non-defined destination format plugin ID, preexisting media types' => [
         'reference_method' => 'id',
         'destination_filter' => NULL,
         'expected_embed_html_attributes' => [
           0 => ['data-entity-id' => '1'] + $default_attributes,
         ],
+        'Preexisting media types' => TRUE,
       ],
-      '"ID" reference method, "entity_embed" destination format plugin ID' => [
+      '"ID" reference method, "entity_embed" destination format plugin ID, preexisting media types' => [
         'reference_method' => 'id',
         'destination_filter' => 'entity_embed',
         'expected_embed_html_attributes' => [
           0 => ['data-entity-id' => '1'] + $default_attributes,
         ],
+        'Preexisting media types' => TRUE,
       ],
-      '"UUID" reference method, non-defined destination format plugin ID' => [
+      '"UUID" reference method, non-defined destination format plugin ID, preexisting media types' => [
         'reference_method' => 'uuid',
         'destination_filter' => NULL,
         'expected_embed_html_attributes' => [
           0 => ['data-entity-uuid' => TRUE] + $default_attributes,
         ],
+        'Preexisting media types' => TRUE,
       ],
-      '"UUID" reference method, "entity_embed" destination format plugin ID' => [
+      '"UUID" reference method, "entity_embed" destination format plugin ID, preexisting media types' => [
         'reference_method' => 'uuid',
         'destination_filter' => 'entity_embed',
         'expected_embed_html_attributes' => [
           0 => ['data-entity-uuid' => TRUE] + $default_attributes,
         ],
+        'Preexisting media types' => TRUE,
       ],
     ];
+
+    // Add 'no initial media types' test cases.
+    $test_cases_without_media_types = [];
+    foreach ($test_cases as $test_case_label => $test_case) {
+      $without_media_label = preg_replace('/preexisting media types$/', 'no media types', $test_case_label);
+      $test_case['Preexisting media types'] = FALSE;
+      $test_cases_without_media_types[$without_media_label] = $test_case;
+    }
+
+    $test_cases += $test_cases_without_media_types;
+
+    return $test_cases;
   }
 
 }
diff --git a/tests/src/Functional/MigrateEmbedMediaTokenToMediaEmbedTest.php b/tests/src/Functional/MigrateEmbedMediaTokenToMediaEmbedTest.php
index 323ab0d..e705bb2 100644
--- a/tests/src/Functional/MigrateEmbedMediaTokenToMediaEmbedTest.php
+++ b/tests/src/Functional/MigrateEmbedMediaTokenToMediaEmbedTest.php
@@ -28,14 +28,22 @@ class MigrateEmbedMediaTokenToMediaEmbedTest extends MigrateEmbedMediaTokenTestB
    *   The method of how embed media is referenced.
    * @param array $extra_modules
    *   Additional modules to enable before the migration.
+   * @param bool $preexisting_media_types
+   *   Whether media types should be present before the migration.
    *
    * @dataProvider providerMediaEmbedTransform
    */
-  public function testMediaTokenToMediaEmbedTransform(string $reference_method, array $extra_modules) {
-    // The media embed filter exists only from Drupal 8.8+.
-    if (version_compare(\Drupal::VERSION, '8.8', '<')) {
-      $this->pass('The media embed filter exists only from Drupal 8.8+.');
-      return;
+  public function testMediaTokenToMediaEmbedTransform(string $reference_method, array $extra_modules, bool $preexisting_media_types) {
+    // Delete preexisting media types.
+    $media_types = $this->container->get('entity_type.manager')
+      ->getStorage('media_type')
+      ->loadMultiple();
+    foreach ($media_types as $media_type) {
+      $media_type->delete();
+    }
+
+    if ($preexisting_media_types) {
+      $this->createStandardMediaTypes(TRUE);
     }
 
     // Set the reference method.
@@ -68,27 +76,43 @@ class MigrateEmbedMediaTokenToMediaEmbedTest extends MigrateEmbedMediaTokenTestB
    *   The test cases.
    */
   public function providerMediaEmbedTransform() {
-    return [
+    $test_cases = [
       // ID reference method. This should be neutral for media_embed token
       // transform destination.
-      'ID reference method, no additional modules' => [
+      'ID reference method, no additional modules, preexisting media types' => [
         'reference method' => 'id',
         'additional modules' => [],
+        'Preexisting media types' => TRUE,
       ],
-      'ID reference method, Entity Embed installed' => [
+      'ID reference method, Entity Embed installed, preexisting media types' => [
         'reference method' => 'id',
         'additional modules' => ['entity_embed'],
+        'Preexisting media types' => TRUE,
       ],
       // UUID reference method.
-      'UUID reference method, no additional modules' => [
+      'UUID reference method, no additional modules, preexisting media types' => [
         'reference method' => 'uuid',
         'additional modules' => [],
+        'Preexisting media types' => TRUE,
       ],
-      'UUID reference method, Entity Embed installed' => [
+      'UUID reference method, Entity Embed installed, preexisting media types' => [
         'reference method' => 'uuid',
         'additional modules' => ['entity_embed'],
+        'Preexisting media types' => TRUE,
       ],
     ];
+
+    // Add 'no initial media types' test cases.
+    $test_cases_without_media_types = [];
+    foreach ($test_cases as $test_case_label => $test_case) {
+      $without_media_label = preg_replace('/preexisting media types$/', 'no media types', $test_case_label);
+      $test_case['Preexisting media types'] = FALSE;
+      $test_cases_without_media_types[$without_media_label] = $test_case;
+    }
+
+    $test_cases += $test_cases_without_media_types;
+
+    return $test_cases;
   }
 
 }
diff --git a/tests/src/Functional/MigrateMediaFromNonMediaSourceTest.php b/tests/src/Functional/MigrateMediaFromNonMediaSourceTest.php
index e902a02..44d8926 100644
--- a/tests/src/Functional/MigrateMediaFromNonMediaSourceTest.php
+++ b/tests/src/Functional/MigrateMediaFromNonMediaSourceTest.php
@@ -34,7 +34,7 @@ class MigrateMediaFromNonMediaSourceTest extends MigrateMediaTestBase {
    *
    * @dataProvider providerTestPlainFileToMediaMigration
    */
-  public function testPlainFileToMediaMigration(bool $classic_node_migration, bool $initial_media_types) {
+  public function testPlainFileToMediaMigration(bool $classic_node_migration, bool $preexisting_media_types) {
     $this->setClassicNodeMigration($classic_node_migration);
     // Delete preexisting media types.
     $media_types = $this->container->get('entity_type.manager')
@@ -44,7 +44,7 @@ class MigrateMediaFromNonMediaSourceTest extends MigrateMediaTestBase {
       $media_type->delete();
     }
 
-    if ($initial_media_types) {
+    if ($preexisting_media_types) {
       $this->createStandardMediaTypes(TRUE);
     }
 
@@ -93,19 +93,19 @@ class MigrateMediaFromNonMediaSourceTest extends MigrateMediaTestBase {
     $test_cases = [
       'Classic node migration, no initial media types' => [
         'Classic node migration' => TRUE,
-        'Initial media types' => FALSE,
+        'Preexisting media types' => FALSE,
       ],
       'Complete node migration, no initial media types' => [
         'Classic node migration' => FALSE,
-        'Initial media types' => FALSE,
+        'Preexisting media types' => FALSE,
       ],
       'Classic node migration, preexisting media types' => [
         'Classic node migration' => TRUE,
-        'Initial media types' => TRUE,
+        'Preexisting media types' => TRUE,
       ],
       'Complete node migration, preexisting media types' => [
         'Classic node migration' => FALSE,
-        'Initial media types' => TRUE,
+        'Preexisting media types' => TRUE,
       ],
     ];
 
diff --git a/tests/src/Functional/MigrateMediaTest.php b/tests/src/Functional/MigrateMediaTest.php
index 1bf3594..8006931 100644
--- a/tests/src/Functional/MigrateMediaTest.php
+++ b/tests/src/Functional/MigrateMediaTest.php
@@ -11,15 +11,33 @@ namespace Drupal\Tests\media_migration\Functional;
  */
 class MigrateMediaTest extends MigrateMediaTestBase {
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedEntities() {
+    return [];
+  }
+
   /**
    * Tests the result of the media migration.
    *
    * @dataProvider providerTestMediaMigrate
    */
-  public function testMediaMigrate(bool $classic_node_migration) {
+  public function testMediaMigrate(bool $classic_node_migration, bool $preexisting_media_types) {
     $this->setClassicNodeMigration($classic_node_migration);
+    // Delete preexisting media types.
+    $media_types = $this->container->get('entity_type.manager')
+      ->getStorage('media_type')
+      ->loadMultiple();
+    foreach ($media_types as $media_type) {
+      $media_type->delete();
+    }
+
+    if ($preexisting_media_types) {
+      $this->createStandardMediaTypes(TRUE);
+    }
 
-    $this->assertMigrateUpgradeViaUi();
+    $this->assertMigrateUpgradeViaUi(FALSE);
     $this->assertMediaMigrationResults();
 
     $assert_session = $this->assertSession();
@@ -101,6 +119,9 @@ class MigrateMediaTest extends MigrateMediaTestBase {
     $this->getSession()->getPage()->clickLink('red.jpeg');
     $this->assertSession()->statusCodeEquals(200);
 
+    $this->assertMedia4FieldValues();
+    $this->assertMedia5FieldValues();
+
     $this->assertMedia6FieldValues();
     $this->drupalGet('media/6/edit');
     $assert_session->statusCodeEquals(200);
@@ -143,6 +164,8 @@ class MigrateMediaTest extends MigrateMediaTestBase {
     $this->assertMedia10FieldValues();
     $this->assertMedia11FieldValues();
     $this->assertMedia12FieldValues();
+
+    $this->assertNode1FieldValues();
   }
 
   /**
@@ -153,11 +176,21 @@ class MigrateMediaTest extends MigrateMediaTestBase {
    */
   public function providerTestMediaMigrate() {
     $test_cases = [
-      'Classic node migration' => [
+      'Classic node migration, no initial media types' => [
+        'Classic node migration' => TRUE,
+        'Preexisting media types' => FALSE,
+      ],
+      'Complete node migration, no initial media types' => [
+        'Classic node migration' => FALSE,
+        'Preexisting media types' => FALSE,
+      ],
+      'Classic node migration, preexisting media types' => [
         'Classic node migration' => TRUE,
+        'Preexisting media types' => TRUE,
       ],
-      'Complete node migration' => [
+      'Complete node migration, preexisting media types' => [
         'Classic node migration' => FALSE,
+        'Preexisting media types' => TRUE,
       ],
     ];
 
diff --git a/tests/src/Functional/MigrateMediaTestBase.php b/tests/src/Functional/MigrateMediaTestBase.php
index 2dc13b5..9b37248 100644
--- a/tests/src/Functional/MigrateMediaTestBase.php
+++ b/tests/src/Functional/MigrateMediaTestBase.php
@@ -57,6 +57,7 @@ abstract class MigrateMediaTestBase extends MigrateUpgradeTestBase {
     'image',
     'media',
     'media_migration',
+    'media_migration_test_oembed',
     'migrate_drupal_ui',
     'node',
   ];
@@ -216,6 +217,8 @@ abstract class MigrateMediaTestBase extends MigrateUpgradeTestBase {
         1 => 'Blue PNG',
         2 => 'green.jpg',
         3 => 'red.jpeg',
+        4 => 'DrupalCon Amsterdam 2019: Keynote - Driesnote',
+        5 => 'Responsive Images in Drupal 8',
         6 => 'LICENSE.txt',
         7 => 'yellow.jpg',
         8 => 'video.webm',
diff --git a/tests/src/Kernel/Migrate/MediaMigrationPlainTest.php b/tests/src/Kernel/Migrate/MediaMigrationPlainTest.php
index e161dea..e7db80a 100644
--- a/tests/src/Kernel/Migrate/MediaMigrationPlainTest.php
+++ b/tests/src/Kernel/Migrate/MediaMigrationPlainTest.php
@@ -51,10 +51,10 @@ class MediaMigrationPlainTest extends MediaMigrationTestBase {
    *
    * @dataProvider providerTestPlainFileToMediaMigration
    */
-  public function testPlainFileToMediaMigration(bool $classic_node_migration, bool $initial_media_types) {
+  public function testPlainFileToMediaMigration(bool $classic_node_migration, bool $preexisting_media_types) {
     $this->setClassicNodeMigration($classic_node_migration);
 
-    if ($initial_media_types) {
+    if ($preexisting_media_types) {
       $this->createStandardMediaTypes(TRUE);
     }
 
@@ -133,19 +133,19 @@ class MediaMigrationPlainTest extends MediaMigrationTestBase {
     $test_cases = [
       'Classic node migration, no initial media types' => [
         'Classic node migration' => TRUE,
-        'Initial media types' => FALSE,
+        'Preexisting media types' => FALSE,
       ],
       'Complete node migration, no initial media types' => [
         'Classic node migration' => FALSE,
-        'Initial media types' => FALSE,
+        'Preexisting media types' => FALSE,
       ],
       'Classic node migration, preexisting media types' => [
         'Classic node migration' => TRUE,
-        'Initial media types' => TRUE,
+        'Preexisting media types' => TRUE,
       ],
       'Complete node migration, preexisting media types' => [
         'Classic node migration' => FALSE,
-        'Initial media types' => TRUE,
+        'Preexisting media types' => TRUE,
       ],
     ];
 
diff --git a/tests/src/Kernel/Migrate/MediaMigrationTest.php b/tests/src/Kernel/Migrate/MediaMigrationTest.php
index ed57200..9fa8de0 100644
--- a/tests/src/Kernel/Migrate/MediaMigrationTest.php
+++ b/tests/src/Kernel/Migrate/MediaMigrationTest.php
@@ -30,6 +30,7 @@ class MediaMigrationTest extends MediaMigrationTestBase {
     'link',
     'media',
     'media_migration',
+    'media_migration_test_oembed',
     'menu_ui',
     'migrate',
     'migrate_drupal',
@@ -49,8 +50,10 @@ class MediaMigrationTest extends MediaMigrationTestBase {
    *
    * @dataProvider providerTestMediaMigration
    */
-  public function testMediaMigration(string $destination_token, string $reference_method, bool $classic_node_migration, array $expected_node1_embed_attributes) {
-    $this->createStandardMediaTypes();
+  public function testMediaMigration(string $destination_token, string $reference_method, bool $classic_node_migration, array $expected_node1_embed_attributes, bool $preexisting_media_types) {
+    if ($preexisting_media_types) {
+      $this->createStandardMediaTypes();
+    }
     $this->setEmbedTokenDestinationFilterPlugin($destination_token);
     $this->setEmbedMediaReferenceMethod($reference_method);
     $this->setClassicNodeMigration($classic_node_migration);
@@ -72,11 +75,13 @@ class MediaMigrationTest extends MediaMigrationTestBase {
     $this->executeMigration($file_migration);
     $this->executeMigrations([
       'd7_view_modes',
+      'd7_file_entity_type',
       'd7_field',
       'd7_comment_type',
       'd7_node_type',
       'd7_field_instance',
-      'd7_media_source_field_config',
+      'd7_file_entity_source_field',
+      'd7_file_entity_source_field_config',
     ]);
     // Abusing ::startCollectingMessages(): "d7_field_formatter_settings"
     // triggers PluginNotFoundException: The "file_rendered" plugin does not
@@ -88,6 +93,12 @@ class MediaMigrationTest extends MediaMigrationTestBase {
     $this->stopCollectingMessages();
     $this->executeMigrations([
       'd7_field_instance_widget_settings',
+      'd7_file_entity_widget',
+    ]);
+    $this->executeMigrations([
+      'd7_file_entity_formatter',
+    ]);
+    $this->executeMigrations([
       'd7_embed_button_media',
       'd7_filter_format',
       // @todo: Every migration that uses the "media_wysiwyg_filter" process
@@ -97,7 +108,6 @@ class MediaMigrationTest extends MediaMigrationTestBase {
       'd7_user_role',
       'd7_user',
       $classic_node_migration ? 'd7_node' : 'd7_node_complete',
-      'd7_file_entity_type',
       'd7_file_entity',
     ]);
 
@@ -109,6 +119,8 @@ class MediaMigrationTest extends MediaMigrationTestBase {
     $this->assertMedia1FieldValues();
     $this->assertMedia2FieldValues();
     $this->assertMedia3FieldValues();
+    $this->assertMedia4FieldValues();
+    $this->assertMedia5FieldValues();
     $this->assertMedia6FieldValues();
     $this->assertMedia7FieldValues();
     $this->assertMedia8FieldValues();
@@ -137,7 +149,7 @@ class MediaMigrationTest extends MediaMigrationTestBase {
     $test_cases = [
       // ID reference method. This should be neutral for media_embed token
       // transform destination.
-      'Entity embed destination, ID reference method, classic node migration' => [
+      'Entity embed destination, ID reference method, classic node migration, preexisting media types' => [
         'Destination filter' => 'entity_embed',
         'Reference method' => 'id',
         'Classic node migration' => TRUE,
@@ -148,8 +160,9 @@ class MediaMigrationTest extends MediaMigrationTestBase {
             'data-entity-embed-display' => 'view_mode:media.wysiwyg',
           ] + $default_attributes,
         ],
+        'Preexisting media types' => TRUE,
       ],
-      'Media embed destination, ID reference method, classic node migration' => [
+      'Media embed destination, ID reference method, classic node migration, preexisting media types' => [
         'Destination filter' => 'media_embed',
         'Reference method' => 'id',
         'Classic node migration' => TRUE,
@@ -159,8 +172,9 @@ class MediaMigrationTest extends MediaMigrationTestBase {
             'data-view-mode' => 'wysiwyg',
           ] + $default_attributes,
         ],
+        'Preexisting media types' => TRUE,
       ],
-      'Entity embed destination, ID reference method, complete node migration' => [
+      'Entity embed destination, ID reference method, complete node migration, preexisting media types' => [
         'Destination filter' => 'entity_embed',
         'Reference method' => 'id',
         'Classic node migration' => FALSE,
@@ -171,8 +185,9 @@ class MediaMigrationTest extends MediaMigrationTestBase {
             'data-entity-embed-display' => 'view_mode:media.wysiwyg',
           ] + $default_attributes,
         ],
+        'Preexisting media types' => TRUE,
       ],
-      'Media embed destination, ID reference method, complete node migration' => [
+      'Media embed destination, ID reference method, complete node migration, preexisting media types' => [
         'Destination filter' => 'media_embed',
         'Reference method' => 'id',
         'Classic node migration' => FALSE,
@@ -182,9 +197,10 @@ class MediaMigrationTest extends MediaMigrationTestBase {
             'data-view-mode' => 'wysiwyg',
           ] + $default_attributes,
         ],
+        'Preexisting media types' => TRUE,
       ],
       // UUID reference method.
-      'Entity embed destination, UUID reference method, classic node migration' => [
+      'Entity embed destination, UUID reference method, classic node migration, preexisting media types' => [
         'Destination filter' => 'entity_embed',
         'Reference method' => 'uuid',
         'Classic node migration' => TRUE,
@@ -195,8 +211,9 @@ class MediaMigrationTest extends MediaMigrationTestBase {
             'data-entity-embed-display' => 'view_mode:media.wysiwyg',
           ] + $default_attributes,
         ],
+        'Preexisting media types' => TRUE,
       ],
-      'Media embed destination, UUID reference method, classic node migration' => [
+      'Media embed destination, UUID reference method, classic node migration, preexisting media types' => [
         'Destination filter' => 'media_embed',
         'Reference method' => 'uuid',
         'Classic node migration' => TRUE,
@@ -206,8 +223,9 @@ class MediaMigrationTest extends MediaMigrationTestBase {
             'data-view-mode' => 'wysiwyg',
           ] + $default_attributes,
         ],
+        'Preexisting media types' => TRUE,
       ],
-      'Entity embed destination, UUID reference method, complete node migration' => [
+      'Entity embed destination, UUID reference method, complete node migration, preexisting media types' => [
         'Destination filter' => 'entity_embed',
         'Reference method' => 'uuid',
         'Classic node migration' => FALSE,
@@ -218,8 +236,9 @@ class MediaMigrationTest extends MediaMigrationTestBase {
             'data-entity-embed-display' => 'view_mode:media.wysiwyg',
           ] + $default_attributes,
         ],
+        'Preexisting media types' => TRUE,
       ],
-      'Media embed destination, UUID reference method, complete node migration' => [
+      'Media embed destination, UUID reference method, complete node migration, preexisting media types' => [
         'Destination filter' => 'media_embed',
         'Reference method' => 'uuid',
         'Classic node migration' => FALSE,
@@ -229,9 +248,20 @@ class MediaMigrationTest extends MediaMigrationTestBase {
             'data-view-mode' => 'wysiwyg',
           ] + $default_attributes,
         ],
+        'Preexisting media types' => TRUE,
       ],
     ];
 
+    // Add 'no initial media types' test cases.
+    $test_cases_without_media_types = [];
+    foreach ($test_cases as $test_case_label => $test_case) {
+      $without_media_label = preg_replace('/preexisting media types$/', 'no media types', $test_case_label);
+      $test_case['Preexisting media types'] = FALSE;
+      $test_cases_without_media_types[$without_media_label] = $test_case;
+    }
+
+    $test_cases += $test_cases_without_media_types;
+
     // Drupal 8.8.x only has 'classic' node migrations.
     // @see https://www.drupal.org/node/3105503
     if (version_compare(\Drupal::VERSION, '8.9', '<')) {
diff --git a/tests/src/Kernel/Plugin/migrate/source/d7/FileEntitySourceFieldInstanceTest.php b/tests/src/Kernel/Plugin/migrate/source/d7/FileEntitySourceFieldInstanceTest.php
new file mode 100644
index 0000000..ff81926
--- /dev/null
+++ b/tests/src/Kernel/Plugin/migrate/source/d7/FileEntitySourceFieldInstanceTest.php
@@ -0,0 +1,235 @@
+<?php
+
+namespace Drupal\Tests\media_migration\Kernel\Plugin\migrate\source\d7;
+
+use Drupal\Tests\media_migration\Traits\MediaMigrationSourceDatabaseTrait;
+use Drupal\Tests\media_migration\Traits\MediaMigrationTestTrait;
+use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
+
+/**
+ * Tests the d7_file_entity_source_field_instance migration source plugin.
+ *
+ * @covers \Drupal\media_migration\Plugin\migrate\source\d7\FileEntitySourceFieldInstance
+ * @group media_migration
+ */
+class FileEntitySourceFieldInstanceTest extends MigrateSqlSourceTestBase {
+
+  use MediaMigrationTestTrait;
+  use MediaMigrationSourceDatabaseTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'field',
+    'file',
+    'image',
+    'media',
+    'media_migration',
+    'media_migration_test_dealer_plugins',
+    'migrate_drupal',
+  ];
+
+  /**
+   * {@inheritdoc}
+   *
+   * @dataProvider providerSource
+   *
+   * @requires extension pdo_sqlite
+   */
+  public function testSource(array $source_data, array $expected_data, $expected_count = NULL, array $configuration = [], $high_water = NULL) {
+    $this->createStandardMediaTypes(TRUE);
+    parent::testSource($source_data, $expected_data, $expected_count, $configuration, $high_water);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function providerSource() {
+    $expected_audio_row = [
+      'types' => 'audio',
+      'schemes' => 'public',
+      'bundle' => 'audio',
+      'bundle_label' => 'Audio',
+      'source_plugin_id' => 'audio_file',
+      'source_field_name' => 'field_media_audio_file',
+      'source_field_label' => 'Audio file',
+      'file_extensions' => 'm4a',
+    ];
+    $expected_document_row = [
+      'types' => 'document',
+      'schemes' => 'public',
+      'bundle' => 'document',
+      'bundle_label' => 'Document',
+      'source_plugin_id' => 'file',
+      'source_field_name' => 'field_media_document',
+      'source_field_label' => 'Document',
+      'file_extensions' => 'txt::odt',
+    ];
+    $expected_image_row = [
+      'types' => 'image',
+      'schemes' => 'public',
+      'bundle' => 'image',
+      'bundle_label' => 'Image',
+      'source_plugin_id' => 'image',
+      'source_field_name' => 'field_media_image',
+      'source_field_label' => 'Image',
+      'file_extensions' => 'png::jpg::jpeg::webp',
+    ];
+    $expected_local_video_row = [
+      'types' => 'video',
+      'schemes' => 'public',
+      'bundle' => 'video',
+      'bundle_label' => 'Video',
+      'source_plugin_id' => 'video_file',
+      'source_field_name' => 'field_media_video_file',
+      'source_field_label' => 'Video file',
+      'file_extensions' => 'webm::mp4',
+    ];
+    $expected_youtube_row = [
+      'types' => 'video',
+      'schemes' => 'youtube',
+      'bundle' => 'remote_video',
+      'bundle_label' => 'Remote video',
+      'source_plugin_id' => 'oembed:video',
+      'source_field_name' => 'field_media_oembed_video',
+      'source_field_label' => 'Video URL',
+    ];
+    $expected_vimeo_row = [
+      'types' => 'video',
+      'schemes' => 'vimeo',
+      'bundle' => 'remote_video',
+      'bundle_label' => 'Remote video',
+      'source_plugin_id' => 'oembed:video',
+      'source_field_name' => 'field_media_oembed_video',
+      'source_field_label' => 'Video URL',
+    ];
+    $expected_remote_row = [
+      'types' => 'video',
+      'schemes' => 'youtube::vimeo',
+      'bundle' => 'remote_video',
+      'bundle_label' => 'Remote video',
+      'source_plugin_id' => 'oembed:video',
+      'source_field_name' => 'field_media_oembed_video',
+      'source_field_label' => 'Video URL',
+    ];
+
+    return [
+      'Audio' => [
+        'source_data' => [
+          'file_managed' => static::getFileManagedTableData(),
+          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
+          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
+          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
+          'field_config' => static::getFieldConfigTableData(FALSE),
+        ],
+        'expected_data' => [$expected_audio_row],
+        'count' => 1,
+        'plugin_configuration' => [
+          'types' => 'audio',
+        ],
+      ],
+      'Document' => [
+        'source_data' => [
+          'file_managed' => static::getFileManagedTableData(),
+          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
+          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
+          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
+          'field_config' => static::getFieldConfigTableData(FALSE),
+        ],
+        'expected_data' => [$expected_document_row],
+        'count' => 1,
+        'plugin_configuration' => [
+          'types' => 'document',
+        ],
+      ],
+      // Image: we use a customized Image FileEntityDealer plugin here to
+      // prevent unnecessary DB selects.
+      'Image' => [
+        'source_data' => [
+          'file_managed' => static::getFileManagedTableData(),
+          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
+          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
+          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
+          'field_config' => static::getFieldConfigTableData(FALSE),
+        ],
+        'expected_data' => [$expected_image_row],
+        'count' => 1,
+        'plugin_configuration' => [
+          'types' => 'image',
+          'schemes' => 'public',
+        ],
+      ],
+      'Local video' => [
+        'source_data' => [
+          'file_managed' => static::getFileManagedTableData(),
+          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
+          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
+          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
+          'field_config' => static::getFieldConfigTableData(FALSE),
+        ],
+        'expected_data' => [$expected_local_video_row],
+        'count' => 1,
+        'plugin_configuration' => [
+          'types' => 'video',
+          'schemes' => 'public',
+        ],
+      ],
+      'Youtube video' => [
+        'source_data' => [
+          'file_managed' => static::getFileManagedTableData(),
+        ],
+        'expected_data' => [$expected_youtube_row],
+        'count' => 1,
+        'plugin_configuration' => [
+          'types' => 'video',
+          'schemes' => 'youtube',
+        ],
+      ],
+      'Vimeo video' => [
+        'source_data' => [
+          'file_managed' => static::getFileManagedTableData(),
+        ],
+        'expected_data' => [$expected_vimeo_row],
+        'count' => 1,
+        'plugin_configuration' => [
+          'types' => 'video',
+          'schemes' => 'vimeo',
+        ],
+      ],
+      'All: plugin with default configuration' => [
+        'source_data' => [
+          'file_managed' => static::getFileManagedTableData(),
+          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
+          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
+          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
+          'field_config' => static::getFieldConfigTableData(FALSE),
+        ],
+        'expected_data' => [
+          $expected_image_row,
+          $expected_remote_row,
+          $expected_document_row,
+          $expected_local_video_row,
+          $expected_audio_row,
+        ],
+        'count' => 5,
+        'plugin_configuration' => [],
+      ],
+      'Missing custom scheme as config' => [
+        'source_data' => [
+          'file_managed' => static::getFileManagedTableData(),
+          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
+          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
+          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
+          'field_config' => static::getFieldConfigTableData(FALSE),
+        ],
+        'expected_data' => [],
+        'count' => 0,
+        'plugin_configuration' => [
+          'schemes' => '_missing_custom',
+        ],
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Kernel/Plugin/migrate/source/d7/MediaSourceFieldInstanceTest.php b/tests/src/Kernel/Plugin/migrate/source/d7/MediaSourceFieldInstanceTest.php
deleted file mode 100644
index 56386d4..0000000
--- a/tests/src/Kernel/Plugin/migrate/source/d7/MediaSourceFieldInstanceTest.php
+++ /dev/null
@@ -1,319 +0,0 @@
-<?php
-
-namespace Drupal\Tests\media_migration\Kernel\Plugin\migrate\source\d7;
-
-use Drupal\Tests\media_migration\Traits\MediaMigrationSourceDatabaseTrait;
-use Drupal\Tests\media_migration\Traits\MediaMigrationTestTrait;
-use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
-
-/**
- * Tests the d7_media_source_field_instance_config migration source plugin.
- *
- * @covers \Drupal\media_migration\Plugin\migrate\source\d7\MediaSourceFieldInstance
- * @group media_migration
- */
-class MediaSourceFieldInstanceTest extends MigrateSqlSourceTestBase {
-
-  use MediaMigrationTestTrait;
-  use MediaMigrationSourceDatabaseTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  public static $modules = [
-    'field',
-    'file',
-    'image',
-    'media',
-    'media_migration',
-    'migrate_drupal',
-  ];
-
-  /**
-   * {@inheritdoc}
-   *
-   * @dataProvider providerSource
-   *
-   * @requires extension pdo_sqlite
-   */
-  public function testSource(array $source_data, array $expected_data, $expected_count = NULL, array $configuration = [], $high_water = NULL) {
-    $this->createStandardMediaTypes(TRUE);
-    parent::testSource($source_data, $expected_data, $expected_count, $configuration, $high_water);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function providerSource() {
-    $expected_audio_row = [
-      'field_name' => 'field_media_audio_file',
-      'type' => 'audio',
-      'scheme' => 'public',
-      'file_extensions' => 'mp3 wav aac m4a',
-    ];
-    $expected_document_row = [
-      'field_name' => 'field_media_document',
-      'type' => 'document',
-      'scheme' => 'public',
-      'file_extensions' => 'txt doc docx pdf odt',
-    ];
-    $expected_local_video_row = [
-      'field_name' => 'field_media_video_file',
-      'type' => 'video',
-      'scheme' => 'public',
-      'file_extensions' => 'mp4 webm',
-    ];
-    $expected_image_row_with_default_alt_title_conf = [
-      'field_name' => 'field_media_image',
-      'type' => 'image',
-      'scheme' => 'public',
-      'file_extensions' => 'png gif jpg jpeg webp',
-      'alt_field' => TRUE,
-      'title_field' => FALSE,
-      'alt_field_required' => FALSE,
-      'title_field_required' => FALSE,
-    ];
-
-    return [
-      'Audio' => [
-        'source_data' => [
-          'file_managed' => static::getFileManagedTableData(),
-          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
-          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
-          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
-          'field_config' => static::getFieldConfigTableData(FALSE),
-        ],
-        'expected_data' => [$expected_audio_row],
-        'count' => 1,
-        'plugin_configuration' => [
-          'type' => 'audio',
-        ],
-      ],
-      'Document' => [
-        'source_data' => [
-          'file_managed' => static::getFileManagedTableData(),
-          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
-          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
-          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
-          'field_config' => static::getFieldConfigTableData(FALSE),
-        ],
-        'expected_data' => [$expected_document_row],
-        'count' => 1,
-        'plugin_configuration' => [
-          'type' => 'document',
-        ],
-      ],
-      'Local video' => [
-        'source_data' => [
-          'file_managed' => static::getFileManagedTableData(),
-          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
-          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
-          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
-          'field_config' => static::getFieldConfigTableData(FALSE),
-        ],
-        'expected_data' => [$expected_local_video_row],
-        'count' => 1,
-        'plugin_configuration' => [
-          'type' => 'video',
-          'scheme' => 'public',
-        ],
-      ],
-      'All: plugin with default configuration' => [
-        'source_data' => [
-          'file_managed' => static::getFileManagedTableData(),
-          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
-          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
-          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
-          'field_config' => static::getFieldConfigTableData(FALSE),
-        ],
-        'expected_data' => [
-          $expected_image_row_with_default_alt_title_conf,
-          $expected_document_row,
-          $expected_local_video_row,
-          $expected_audio_row,
-        ],
-        'count' => 4,
-        'plugin_configuration' => [],
-      ],
-      'Missing custom scheme as config' => [
-        'source_data' => [
-          'file_managed' => static::getFileManagedTableData(),
-          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
-          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
-          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
-          'field_config' => static::getFieldConfigTableData(FALSE),
-        ],
-        'expected_data' => [],
-        'count' => 0,
-        'plugin_configuration' => [
-          'scheme' => '_missing_custom',
-        ],
-      ],
-      // Image: no data for alt, nor title; no image fields present, neither alt
-      // nor title fields were required.
-      'Image: alt or title not allowed, not required; no image field' => [
-        'source_data' => [
-          'file_managed' => static::getFileManagedTableData(),
-          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
-          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
-          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
-          'field_config' => static::getFieldConfigTableData(FALSE),
-        ],
-        'expected_data' => [$expected_image_row_with_default_alt_title_conf],
-        'count' => 1,
-        'plugin_configuration' => [
-          'type' => 'image',
-          'scheme' => 'public',
-        ],
-      ],
-      // Image: No data for alt, nor title; image field is present but it does
-      // not show alt or title, neither alt nor title fields were required on
-      // media.
-      'Image: alt or title not allowed, not required; with image field' => [
-        'source_data' => [
-          'file_managed' => static::getFileManagedTableData(),
-          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
-          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
-          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
-          'field_config' => static::getFieldConfigTableData(),
-        ],
-        'expected_data' => [$expected_image_row_with_default_alt_title_conf],
-        'count' => 1,
-        'conf' => [
-          'type' => 'image',
-          'scheme' => 'public',
-        ],
-      ],
-      // Image: Data for alt and title; neither alt nor title fields were
-      // required on media, node image field does not show alt or title.
-      'Image: alt or title not allowed, not required; no field, but alt and title data present' => [
-        'source_data' => [
-          'file_managed' => static::getFileManagedTableData(),
-          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(TRUE),
-          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(TRUE),
-          'field_config_instance' => static::getFieldConfigInstanceTableData(FALSE, FALSE, FALSE, FALSE, FALSE),
-          'field_config' => static::getFieldConfigTableData(),
-        ],
-        'expected_data' => [
-          [
-            'field_name' => 'field_media_image',
-            'type' => 'image',
-            'scheme' => 'public',
-            'file_extensions' => 'png gif jpg jpeg webp',
-            'alt_field' => TRUE,
-            'title_field' => TRUE,
-            'alt_field_required' => FALSE,
-            'title_field_required' => FALSE,
-          ],
-        ],
-        'count' => 1,
-        'conf' => [
-          'type' => 'image',
-          'scheme' => 'public',
-        ],
-      ],
-      // Image: No data for alt, nor title; neither alt nor title fields were
-      // required on media, node image field present without alt or title.
-      'Image: alt or title not allowed, not required; image field present; no alt or title data' => [
-        'source_data' => [
-          'file_managed' => static::getFileManagedTableData(),
-          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
-          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
-          'field_config_instance' => static::getFieldConfigInstanceTableData(TRUE, FALSE, FALSE, FALSE, FALSE),
-          'field_config' => static::getFieldConfigTableData(),
-        ],
-        'expected_data' => [$expected_image_row_with_default_alt_title_conf],
-        'count' => 1,
-        'conf' => [
-          'type' => 'image',
-          'scheme' => 'public',
-        ],
-      ],
-      // Image: No data for alt, nor title; neither alt nor title fields were
-      // required on media, node image fields present with alt and title.
-      'Image: alt and title allowed but not required; image field present, no alt and title data' => [
-        'source_data' => [
-          'file_managed' => static::getFileManagedTableData(),
-          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
-          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
-          'field_config_instance' => static::getFieldConfigInstanceTableData(TRUE, TRUE, TRUE, FALSE, FALSE),
-          'field_config' => static::getFieldConfigTableData(),
-        ],
-        'expected_data' => [
-          [
-            'field_name' => 'field_media_image',
-            'type' => 'image',
-            'scheme' => 'public',
-            'file_extensions' => 'png gif jpg jpeg webp',
-            'alt_field' => TRUE,
-            'title_field' => TRUE,
-            'alt_field_required' => FALSE,
-            'title_field_required' => FALSE,
-          ],
-        ],
-        'count' => 1,
-        'conf' => [
-          'type' => 'image',
-          'scheme' => 'public',
-        ],
-      ],
-      // Image: No data for alt, nor title; alt is required on media, node image
-      // fields present with alt and title.
-      'Image: alt and title allowed, alt required; image field present, no alt and title data' => [
-        'source_data' => [
-          'file_managed' => static::getFileManagedTableData(),
-          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
-          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
-          'field_config_instance' => static::getFieldConfigInstanceTableData(TRUE, TRUE, TRUE, TRUE, FALSE),
-          'field_config' => static::getFieldConfigTableData(),
-        ],
-        'expected_data' => [
-          [
-            'field_name' => 'field_media_image',
-            'type' => 'image',
-            'scheme' => 'public',
-            'file_extensions' => 'png gif jpg jpeg webp',
-            'alt_field' => TRUE,
-            'title_field' => TRUE,
-            'alt_field_required' => TRUE,
-            'title_field_required' => FALSE,
-          ],
-        ],
-        'count' => 1,
-        'conf' => [
-          'type' => 'image',
-          'scheme' => 'public',
-        ],
-      ],
-      // Image: No data for alt, nor title; alt and title are required on media,
-      // node image fields present with alt and title allowed.
-      'Image: alt and title allowed and required; image field present, no alt and title data' => [
-        'source_data' => [
-          'file_managed' => static::getFileManagedTableData(),
-          'field_data_field_file_image_alt_text' => static::getFieldDataFieldFileImageAltTextTableData(FALSE),
-          'field_data_field_file_image_title_text' => static::getFieldDataFieldFileImageTitleTextTableData(FALSE),
-          'field_config_instance' => static::getFieldConfigInstanceTableData(TRUE, TRUE, TRUE, TRUE, TRUE),
-          'field_config' => static::getFieldConfigTableData(),
-        ],
-        'expected_data' => [
-          [
-            'field_name' => 'field_media_image',
-            'type' => 'image',
-            'scheme' => 'public',
-            'file_extensions' => 'png gif jpg jpeg webp',
-            'alt_field' => TRUE,
-            'title_field' => TRUE,
-            'alt_field_required' => TRUE,
-            'title_field_required' => TRUE,
-          ],
-        ],
-        'count' => 1,
-        'conf' => [
-          'type' => 'image',
-          'scheme' => 'public',
-        ],
-      ],
-    ];
-  }
-
-}
diff --git a/tests/src/Traits/MediaMigrationAssertionsForMediaSourceTrait.php b/tests/src/Traits/MediaMigrationAssertionsForMediaSourceTrait.php
index 3dfbbd8..b0a48aa 100644
--- a/tests/src/Traits/MediaMigrationAssertionsForMediaSourceTrait.php
+++ b/tests/src/Traits/MediaMigrationAssertionsForMediaSourceTrait.php
@@ -128,6 +128,52 @@ trait MediaMigrationAssertionsForMediaSourceTrait {
     $this->assertTrue(file_exists($media3_image[0]->getFileUri()));
   }
 
+  /**
+   * Assertions of media 4 (Youtube Apqd4ff0NRI â€“ 2019 Amsterdam Driesnote).
+   */
+  protected function assertMedia4FieldValues() {
+    $media = $this->container->get('entity_type.manager')->getStorage('media')->load(4);
+    assert($media instanceof MediaInterface);
+
+    $this->assertEquals([
+      'mid' => [['value' => '4']],
+      'bundle' => [['target_id' => 'remote_video']],
+      'name' => [['value' => 'DrupalCon Amsterdam 2019: Keynote - Driesnote']],
+      'uid' => [['target_id' => '1']],
+      'status' => [['value' => '1']],
+      'created' => [['value' => '1587726087']],
+      'changed' => [['value' => '1587726087']],
+      'field_media_oembed_video' => [
+        [
+          'value' => 'https://www.youtube.com/watch?v=Apqd4ff0NRI',
+        ],
+      ],
+    ], $this->getImportantEntityProperties($media));
+  }
+
+  /**
+   * Assertions of media 5 (Vimeo 204517230 â€“ Responsive Images).
+   */
+  protected function assertMedia5FieldValues() {
+    $media = $this->container->get('entity_type.manager')->getStorage('media')->load(5);
+    assert($media instanceof MediaInterface);
+
+    $this->assertEquals([
+      'mid' => [['value' => '5']],
+      'bundle' => [['target_id' => 'remote_video']],
+      'name' => [['value' => 'Responsive Images in Drupal 8']],
+      'uid' => [['target_id' => '1']],
+      'status' => [['value' => '1']],
+      'created' => [['value' => '1587730964']],
+      'changed' => [['value' => '1587730964']],
+      'field_media_oembed_video' => [
+        [
+          'value' => 'https://vimeo.com/204517230',
+        ],
+      ],
+    ], $this->getImportantEntityProperties($media));
+  }
+
   /**
    * Assertions of media 6 (LICENSE.txt).
    */
@@ -351,15 +397,45 @@ trait MediaMigrationAssertionsForMediaSourceTrait {
   /**
    * Assertions of node 1.
    */
-  protected function assertNode1FieldValues(array $expected_node1_embed_attributes) {
-    $node1 = $this->container->get('entity_type.manager')->getStorage('node')->load(1);
-    assert($node1 instanceof NodeInterface);
-    $this->assertEquals('Article with embed image media', $node1->label());
-    $this->assertEquals('article', $node1->bundle());
+  protected function assertNode1FieldValues(array $expected_node1_embed_attributes = []) {
+    $node = $this->container->get('entity_type.manager')->getStorage('node')->load(1);
+    assert($node instanceof NodeInterface);
 
-    $node1_body_text = preg_replace('/\s+/', ' ', $node1->body->value);
+    // Ignore body field.
+    $important_properties = $this->getImportantEntityProperties($node);
+    unset($important_properties['body']);
 
-    $this->assertEmbedTokenHtmlTags($node1_body_text, $expected_node1_embed_attributes);
+    $this->assertEquals([
+      'nid' => [['value' => 1]],
+      'type' => [['target_id' => 'article']],
+      'status' => [['value' => 1]],
+      'uid' => [['target_id' => 1]],
+      'title' => [['value' => 'Article with embed image media']],
+      'created' => [['value' => 1587730322]],
+      'changed' => [['value' => 1587730609]],
+      'promote' => [['value' => 1]],
+      'sticky' => [['value' => 0]],
+      'field_image' => [['target_id' => '2']],
+      'field_media' => [
+        ['target_id' => '3'],
+        ['target_id' => '4'],
+      ],
+    ], $important_properties);
+
+    // Test that the image and file fields are referencing media entities.
+    $media_fields = [
+      'field_image' => 1,
+      'field_media' => 2,
+    ];
+    foreach ($media_fields as $field_name => $expected_count) {
+      $referred_entities = $this->getReferencedEntities($node, $field_name, $expected_count);
+      assert($referred_entities[0] instanceof MediaInterface);
+    }
+
+    if (!empty($expected_node1_embed_attributes)) {
+      $node_body_text = preg_replace('/\s+/', ' ', $node->body->value);
+      $this->assertEmbedTokenHtmlTags($node_body_text, $expected_node1_embed_attributes);
+    }
   }
 
   /**
diff --git a/tests/src/Traits/MediaMigrationAssertionsForNonMediaSourceTrait.php b/tests/src/Traits/MediaMigrationAssertionsForNonMediaSourceTrait.php
index ce8e746..6cb27e1 100644
--- a/tests/src/Traits/MediaMigrationAssertionsForNonMediaSourceTrait.php
+++ b/tests/src/Traits/MediaMigrationAssertionsForNonMediaSourceTrait.php
@@ -525,7 +525,7 @@ trait MediaMigrationAssertionsForNonMediaSourceTrait {
       'field_name' => 'field_media_audio_file',
       'entity_type' => 'media',
       'bundle' => 'audio',
-      'label' => 'Audio',
+      'label' => 'Audio file',
       'description' => '',
       'required' => TRUE,
       'translatable' => TRUE,
@@ -558,7 +558,7 @@ trait MediaMigrationAssertionsForNonMediaSourceTrait {
       'field_name' => 'field_media_video_file',
       'entity_type' => 'media',
       'bundle' => 'video',
-      'label' => 'Video',
+      'label' => 'Video file',
       'description' => '',
       'required' => TRUE,
       'translatable' => TRUE,
