diff --git a/MediaEntityController.php b/MediaEntityController.php
deleted file mode 100644
index 7a0e917..0000000
--- a/MediaEntityController.php
+++ /dev/null
@@ -1,215 +0,0 @@
-<?php
-/**
- *  DO NOT EDIT THIS. IT WILL BE REMOVED IN THE NEXT RELEASE.
- *   This file is only here so that the class autoloader doesn't barf.
- *
- * It has been moved to includes
- */
-/**
- * Extends DrupalEntityControllerInterface.
- *
- * @see: http://drupal.org/project/entity_api (perhaps we should be using this)
- */
-class MediaEntityController extends DrupalDefaultEntityController {
-
-  public function load($ids = array(), $conditions = array()) {
-    $this->ids = $ids;
-    $this->conditions = $conditions;
-
-    $entities = array();
-
-    // Revisions are not statically cached, and require a different query to
-    // other conditions, so separate the revision id into its own variable.
-    if ($this->revisionKey && isset($this->conditions[$this->revisionKey])) {
-      $this->revisionId = $this->conditions[$this->revisionKey];
-      unset($this->conditions[$this->revisionKey]);
-    }
-    else {
-      $this->revisionId = FALSE;
-    }
-
-    // Create a new variable which is either a prepared version of the $ids
-    // array for later comparison with the entity cache, or FALSE if no $ids
-    // were passed. The $ids array is reduced as items are loaded from cache,
-    // and we need to know if it's empty for this reason to avoid querying the
-    // database when all requested entities are loaded from cache.
-    $passed_ids = !empty($this->ids) ? array_flip($this->ids) : FALSE;
-    // Try to load entities from the static cache, if the entity type supports
-    // static caching.
-    if ($this->cache) {
-      $entities += $this->cacheGet($this->ids, $this->conditions);
-      // If any entities were loaded, remove them from the ids still to load.
-      if ($passed_ids) {
-        $this->ids = array_keys(array_diff_key($passed_ids, $entities));
-      }
-    }
-
-    // Load any remaining entities from the database. This is the case if $ids
-    // is set to FALSE (so we load all entities), if there are any ids left to
-    // load, if loading a revision, or if $conditions was passed without $ids.
-    if ($this->ids === FALSE || $this->ids || $this->revisionId || ($this->conditions && !$passed_ids)) {
-      // Build the query.
-      $this->buildQuery();
-      $queried_entities = $this->query
-        ->execute()
-        ->fetchAllAssoc($this->idKey);
-    }
-
-    // Pass all entities loaded from the database through $this->attachLoad(),
-    // which attaches fields (if supported by the entity type) and calls the
-    // entity type specific load callback, for example hook_node_load().
-    if (!empty($queried_entities)) {
-      $this->attachLoad($queried_entities);
-      $entities += $queried_entities;
-    }
-
-    if ($this->cache) {
-      // Add entities to the cache if we are not loading a revision.
-      if (!empty($queried_entities) && !$this->revisionId) {
-        $this->cacheSet($queried_entities);
-      }
-    }
-
-    // Ensure that the returned array is ordered the same as the original
-    // $ids array if this was passed in and remove any invalid ids.
-    if ($passed_ids) {
-      // Remove any invalid ids from the array.
-      $passed_ids = array_intersect_key($passed_ids, $entities);
-      foreach ($entities as $entity) {
-        $passed_ids[$entity->{$this->idKey}] = $entity;
-      }
-      $entities = $passed_ids;
-    }
-
-    return $entities;
-  }
-  protected function buildQuery() {
-    $this->query = db_select($this->entityInfo['base table'], 'base');
-
-    $this->query->addTag($this->entityType . '_load_multiple');
-
-    if ($this->revisionId) {
-      $this->query->join($this->revisionTable, 'revision', "revision.{$this->idKey} = base.{$this->idKey} AND revision.{$this->revisionKey} = :revisionId", array(':revisionId' => $this->revisionId));
-    }
-    elseif ($this->revisionKey) {
-      $this->query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}");
-    }
-
-    // Add fields from the {entity} table.
-    $entity_fields = drupal_schema_fields_sql($this->entityInfo['base table']);
-
-    if ($this->revisionKey) {
-      // Add all fields from the {entity_revision} table.
-      $entity_revision_fields = drupal_map_assoc(drupal_schema_fields_sql($this->revisionTable));
-      // The id field is provided by entity, so remove it.
-      unset($entity_revision_fields[$this->idKey]);
-
-      // Change timestamp to revision_timestamp, and revision uid to
-      // revision_uid before adding them to the query.
-      // TODO: This is node specific and has to be moved into NodeController.
-      unset($entity_revision_fields['timestamp']);
-      $this->query->addField('revision', 'timestamp', 'revision_timestamp');
-      unset($entity_revision_fields['uid']);
-      $this->query->addField('revision', 'uid', 'revision_uid');
-
-      // Remove all fields from the base table that are also fields by the same
-      // name in the revision table.
-      $entity_field_keys = array_flip($entity_fields);
-      foreach ($entity_revision_fields as $key => $name) {
-        if (isset($entity_field_keys[$name])) {
-          unset($entity_fields[$entity_field_keys[$name]]);
-        }
-      }
-      $this->query->fields('revision', $entity_revision_fields);
-    }
-
-    $this->query->fields('base', $entity_fields);
-
-    if ($this->ids) {
-      $this->query->condition("base.{$this->idKey}", $this->ids, 'IN');
-    }
-    if ($this->conditions) {
-      foreach ($this->conditions as $field => $value) {
-        $this->query->condition('base.' . $field, $value);
-      }
-    }
-  }
-
-  /**
-   * Attach data to entities upon loading.
-   *
-   * This will attach fields, if the entity is fieldable. It calls
-   * hook_entity_load() for modules which need to add data to all entities.
-   * It also calls hook_TYPE_load() on the loaded entities. For example
-   * hook_node_load() or hook_user_load(). If your hook_TYPE_load()
-   * expects special parameters apart from the queried entities, you can set
-   * $this->hookLoadArguments prior to calling the method.
-   * See NodeController::attachLoad() for an example.
-   */
-  protected function attachLoad(&$queried_entities) {
-    // Attach fields.
-    if ($this->entityInfo['fieldable']) {
-      if ($this->revisionId) {
-        field_attach_load_revision($this->entityType, $queried_entities);
-      }
-      else {
-        field_attach_load($this->entityType, $queried_entities);
-      }
-    }
-
-    // Call hook_entity_load().
-    foreach (module_implements('entity_load') as $module) {
-      $function = $module . '_entity_load';
-      $function($queried_entities, $this->entityType);
-    }
-    // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
-    // always the queried entities, followed by additional arguments set in
-    // $this->hookLoadArguments.
-    $args = array_merge(array($queried_entities), $this->hookLoadArguments);
-    foreach (module_implements($this->entityInfo['load hook']) as $module) {
-      call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args);
-    }
-  }
-
-  /**
-   * Get entities from the static cache.
-   *
-   * @param $ids
-   *   If not empty, return entities that match these IDs.
-   * @param $conditions
-   *   If set, return entities that match all of these conditions.
-   */
-  protected function cacheGet($ids, $conditions = array()) {
-    $entities = array();
-    // Load any available entities from the internal cache.
-    if (!empty($this->entityCache) && !$this->revisionId) {
-      if ($ids) {
-        $entities += array_intersect_key($this->entityCache, array_flip($ids));
-      }
-      // If loading entities only by conditions, fetch all available entities
-      // from the cache. Entities which don't match are removed later.
-      elseif ($conditions) {
-        $entities = $this->entityCache;
-      }
-    }
-
-    // Exclude any entities loaded from cache if they don't match $conditions.
-    // This ensures the same behavior whether loading from memory or database.
-    if ($conditions) {
-      foreach ($entities as $entity) {
-        $entity_values = (array) $entity;
-        if (array_diff_assoc($conditions, $entity_values)) {
-          unset($entities[$entity->{$this->idKey}]);
-        }
-      }
-    }
-    return $entities;
-  }
-
-  /**
-   * Store entities in the static entity cache.
-   */
-  protected function cacheSet($entities) {
-    $this->entityCache += $entities;
-  }
-}
diff --git a/MediaReadOnlyStreamWrapper.inc b/MediaReadOnlyStreamWrapper.inc
index e771c14..b8c0020 100644
--- a/MediaReadOnlyStreamWrapper.inc
+++ b/MediaReadOnlyStreamWrapper.inc
@@ -1,5 +1,9 @@
 <?php
-
+/**
+ * DO NOT EDIT. This file will be removed in the next release. It has been moved
+ * to the includes folder, but is retained here so that the autoloader doesn't
+ * trigger a fatal error before media_update_7015() runs.
+ */
 /**
  * A base class for Resource Stream Wrappers.
  *
diff --git a/file_entity/file_entity.admin.inc b/file_entity/file_entity.admin.inc
new file mode 100644
index 0000000..18ff8b5
--- /dev/null
+++ b/file_entity/file_entity.admin.inc
@@ -0,0 +1,177 @@
+<?php
+
+/**
+ * @file
+ * Administrative interface for file type configuration.
+ */
+
+/**
+ * Displays the file type admin overview page.
+ */
+function file_entity_list_types_page() {
+  $types = file_info_file_types();
+  $entity_info = entity_get_info('file');
+  $field_ui = module_exists('field_ui');
+  $header = array(
+    array('data' => t('Name')),
+    array('data' => t('Operations'), 'colspan' => $field_ui ? '3' : '1'),
+  );
+  $rows = array();
+
+  foreach ($types as $type => $info) {
+    $row = array(array('data' => theme('file_entity_file_type_overview', $info)));
+    $path = isset($entity_info['bundles'][$type]['admin']['real path']) ? $entity_info['bundles'][$type]['admin']['real path'] : NULL;
+    if ($field_ui) {
+      $row[] = array('data' => isset($path) ? l(t('manage fields'), $path . '/fields') : '');
+      $row[] = array('data' => isset($path) ? l(t('manage display'), $path . '/display') : '');
+    }
+    $row[] = array('data' => isset($path) ? l(t('manage file display'), $path . '/file-display') : '');
+    $rows[] = $row;
+  }
+
+  $build['file_type_table'] = array(
+    '#theme' => 'table',
+    '#header' => $header,
+    '#rows' => $rows,
+    '#empty' => t('No file types available.'),
+  );
+
+  return $build;
+}
+
+/**
+ * Form callback; presents file display settings for a given view mode.
+ */
+function file_entity_file_display_form($form, &$form_state, $file_type, $view_mode) {
+  $form['#file_type'] = $file_type;
+  $form['#view_mode'] = $view_mode;
+  $form['#tree'] = TRUE;
+  $form['#attached']['js'][] = drupal_get_path('module', 'file_entity') . '/file_entity.admin.js';
+
+  // Retrieve available formatters for this file type and load all configured
+  // filters for existing text formats.
+  $formatters = file_info_formatter_types();
+  foreach ($formatters as $name => $formatter) {
+    if (isset($formatter['file types']) && !in_array($file_type, $formatter['file types'])) {
+      unset ($formatters[$name]);
+    }
+  }
+  $current_displays = variable_get('file_displays');
+  $current_displays = isset($current_displays[$file_type][$view_mode]) ? $current_displays[$file_type][$view_mode] : array();
+
+  // Formatter status.
+  $form['displays']['status'] = array(
+    '#type' => 'item',
+    '#title' => t('Enabled displays'),
+    '#prefix' => '<div id="file-displays-status-wrapper">',
+    '#suffix' => '</div>',
+  );
+  $i=0;
+  foreach ($formatters as $name => $formatter) {
+    $form['displays']['status'][$name] = array(
+      '#type' => 'checkbox',
+      '#title' => $formatter['label'],
+      '#default_value' => !empty($current_displays[$name]['status']),
+      '#description' => isset($formatter['description']) ? $formatter['description'] : NULL,
+      '#parents' => array('displays', $name, 'status'),
+      '#weight' => $formatter['weight'] + $i/1000,
+    );
+    $i++;
+  }
+
+  // Formatter order (tabledrag).
+  $form['displays']['order'] = array(
+    '#type' => 'item',
+    '#title' => t('Display precedence order'),
+    '#theme' => 'file_entity_file_display_order',
+  );
+  foreach ($formatters as $name => $formatter) {
+    $form['displays']['order'][$name]['label'] = array(
+      '#markup' => check_plain($formatter['label']),
+    );
+    $form['displays']['order'][$name]['weight'] = array(
+      '#type' => 'weight',
+      '#title' => t('Weight for @title', array('@title' => $formatter['label'])),
+      '#title_display' => 'invisible',
+      '#delta' => 50,
+      '#default_value' => isset($current_displays[$name]['weight']) ? $current_displays[$name]['weight'] : 0,
+      '#parents' => array('displays', $name, 'weight'),
+    );
+    $form['displays']['order'][$name]['#weight'] = $form['displays']['order'][$name]['weight']['#default_value'];
+  }
+
+  // Formatter settings.
+  $form['display_settings_title'] = array(
+    '#type' => 'item',
+    '#title' => t('Display settings'),
+  );
+  $form['display_settings'] = array(
+    '#type' => 'vertical_tabs',
+  );
+  $i=0;
+  foreach ($formatters as $name => $formatter) {
+    if (isset($formatter['settings callback']) && ($function = $formatter['settings callback']) && function_exists($function)) {
+      $defaults = isset($formatter['default settings']) ? $formatter['default settings'] : array();
+      $settings = isset($current_displays[$name]['settings']) ? $current_displays[$name]['settings'] : array();
+      $settings += $defaults;
+      $settings_form = $function($form, $form_state, $settings, $name, $file_type, $view_mode);
+      if (!empty($settings_form)) {
+        $form['displays']['settings'][$name] = array(
+          '#type' => 'fieldset',
+          '#title' => $formatter['label'],
+          '#parents' => array('displays', $name, 'settings'),
+          '#group' => 'display_settings',
+          '#weight' => $formatter['weight'] + $i/1000,
+        ) + $settings_form;
+      }
+    }
+    $i++;
+  }
+
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
+
+  return $form;
+}
+
+/**
+ * Process file display settings form submissions.
+ */
+function file_entity_file_display_form_submit($form, &$form_state) {
+  $displays = isset($form_state['values']['displays']) ? $form_state['values']['displays'] : array();
+  $file_displays = variable_get('file_displays');
+  $file_displays[$form['#file_type']][$form['#view_mode']] = $displays;
+  variable_set('file_displays', $file_displays);
+  drupal_set_message(t('Your settings have been saved.'));
+}
+
+/**
+ * Returns HTML for a file type label and description for the file type admin overview page.
+ */
+function theme_file_entity_file_type_overview($variables) {
+  return check_plain($variables['label']) . '<div class="description">' . $variables['description'] . '</div>';
+}
+
+/**
+ * Returns HTML for a file display's display order table.
+ */
+function theme_file_entity_file_display_order($variables) {
+  $element = $variables['element'];
+
+  $rows = array();
+  foreach (element_children($element, TRUE) as $name) {
+    $element[$name]['weight']['#attributes']['class'][] = 'file-display-order-weight';
+    $rows[] = array(
+      'data' => array(
+        drupal_render($element[$name]['label']),
+        drupal_render($element[$name]['weight']),
+      ),
+      'class' => array('draggable'),
+    );
+  }
+  $output = drupal_render_children($element);
+  $output .= theme('table', array('rows' => $rows, 'attributes' => array('id' => 'file-displays-order')));
+  drupal_add_tabledrag('file-displays-order', 'order', 'sibling', 'file-display-order-weight', NULL, NULL, TRUE);
+
+  return $output;
+}
diff --git a/file_entity/file_entity.admin.js b/file_entity/file_entity.admin.js
new file mode 100644
index 0000000..8053958
--- /dev/null
+++ b/file_entity/file_entity.admin.js
@@ -0,0 +1,45 @@
+
+(function ($) {
+
+Drupal.behaviors.fileDisplayStatus = {
+  attach: function (context, settings) {
+    $('#file-displays-status-wrapper input.form-checkbox', context).once('display-status', function () {
+      var $checkbox = $(this);
+      // Retrieve the tabledrag row belonging to this display.
+      var $row = $('#' + $checkbox.attr('id').replace(/-status$/, '-weight'), context).closest('tr');
+      // Retrieve the vertical tab belonging to this display.
+      var tab = $('#' + $checkbox.attr('id').replace(/-status$/, '-settings'), context).data('verticalTab');
+
+      // Bind click handler to this checkbox to conditionally show and hide the
+      // display's tableDrag row and vertical tab pane.
+      $checkbox.bind('click.displayStatusUpdate', function () {
+        if ($checkbox.is(':checked')) {
+          $row.show();
+          if (tab) {
+            tab.tabShow().updateSummary();
+          }
+        }
+        else {
+          $row.hide();
+          if (tab) {
+            tab.tabHide().updateSummary();
+          }
+        }
+        // Restripe table after toggling visibility of table row.
+        Drupal.tableDrag['file-displays-order'].restripeTable();
+      });
+
+      // Attach summary for configurable displays (only for screen-readers).
+      if (tab) {
+        tab.fieldset.drupalSetSummary(function (tabContext) {
+          return $checkbox.is(':checked') ? Drupal.t('Enabled') : Drupal.t('Disabled');
+        });
+      }
+
+      // Trigger our bound click handler to update elements to initial state.
+      $checkbox.triggerHandler('click.displayStatusUpdate');
+    });
+  }
+};
+
+})(jQuery);
diff --git a/file_entity/file_entity.api.php b/file_entity/file_entity.api.php
new file mode 100644
index 0000000..10fe123
--- /dev/null
+++ b/file_entity/file_entity.api.php
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the File Entity module.
+ */
+
+/**
+ * Define file types.
+ *
+ * @return
+ *   An array whose keys are file type names and whose values are arrays
+ *   describing the file type, with the following key/value pairs:
+ *   - label: The human-readable name of the file type.
+ *   - claim callback: The name of the function that returns if a given file is
+ *     of this type. See hook_file_type_TYPE_claim() for details.
+ *   - default view callback: (optional) The name of the function that returns a
+ *     drupal_render() array for displaying the file. Used when there are no
+ *     administrator configured file formatters, or none of the configured ones
+ *     return a display. See hook_file_type_TYPE_default_view() for details.
+ *   - description: (optional) A short description of the file type.
+ *   - weight: (optional) A number defining the order in which the 'claim
+ *     callback' function for this type is called relative to the claim
+ *     callbacks of other defined types, when the type of a file needs to be
+ *     determined. The type with the lowest weighted claim callback to return
+ *     TRUE is assigned to the file. Also, on administrative pages listing file
+ *     types, the types are ordered by weight.
+ *   - admin: (optional) An array of information, to be added to the
+ *     ['bundles'][TYPE]['admin'] entry for the 'file' entity type, thereby
+ *     controlling the path at which Field UI pages are attached for this file
+ *     type, and which users may access them. Defaults to attaching the Field UI
+ *     pages to the admin/config/media/file-types/manage/TYPE path and requiring
+ *     'administer site configuration' permission. See hook_entity_info() for
+ *     details about this array. This value can also be set to NULL to suppress
+ *     Field UI pages from attaching at all for this file type.
+ *
+ * @see hook_file_type_info_alter()
+ */
+function hook_file_type_info() {
+  return array(
+    'image' => array(
+      'label' => t('Image'),
+    ),
+  );
+}
+
+/**
+ * Perform alterations on file types.
+ *
+ * @param $info
+ *   Array of information on file types exposed by hook_file_type_info()
+ *   implementations.
+ */
+function hook_file_type_info_alter(&$info) {
+  // @todo Add example.
+}
+
+/**
+ * @todo Add documentation.
+ *
+ * Note: This is not really a hook. The function name is manually specified via
+ * 'claim callback' in hook_file_type_info(), with this recommended
+ * callback name pattern.
+ */
+function hook_file_type_TYPE_claim($file, $type) {
+}
+
+/**
+ * @todo Add documentation.
+ *
+ * Note: This is not really a hook. The function name is manually specified via
+ * 'default view callback' in hook_file_type_info(), with this recommended
+ * callback name pattern.
+ */
+function hook_file_type_TYPE_default_view($file, $view_mode, $langcode) {
+}
+
+/**
+ * Define file formatters.
+ *
+ * @return
+ *   An array whose keys are file formatter names and whose values are arrays
+ *   describing the formatter.
+ *
+ * @todo Document key/value pairs that comprise a formatter.
+ *
+ * @see hook_file_formatter_info_alter()
+ */
+function hook_file_formatter_info() {
+  // @todo Add example.
+}
+
+/**
+ * Perform alterations on file formatters.
+ *
+ * @param $info
+ *   Array of information on file formatters exposed by
+ *   hook_file_formatter_info() implementations.
+ */
+function hook_file_formatter_info_alter(&$info) {
+  // @todo Add example.
+}
+
+/**
+ * @todo Add documentation.
+ *
+ * Note: This is not really a hook. The function name is manually specified via
+ * 'view callback' in hook_file_formatter_info(), with this recommended callback
+ * name pattern.
+ */
+function hook_file_formatter_FORMATTER_view($file, $display, $langcode) {
+}
+
+/**
+ * @todo Add documentation.
+ *
+ * Note: This is not really a hook. The function name is manually specified via
+ * 'settings callback' in hook_file_formatter_info(), with this recommended
+ * callback name pattern.
+ */
+function hook_file_formatter_FORMATTER_settings($form, &$form_state, $settings) {
+}
+
+/**
+ * @todo Add documentation.
+ */
+function hook_file_displays_alter($displays, $file, $view_mode) {
+}
+
+/**
+ * @todo Add documentation.
+ */
+function hook_file_view($file, $view_mode, $langcode) {
+}
+
+/**
+ * @todo Add documentation.
+ */
+function hook_file_view_alter($build, $type) {
+}
diff --git a/file_entity/file_entity.file_api.inc b/file_entity/file_entity.file_api.inc
new file mode 100644
index 0000000..d83cdc4
--- /dev/null
+++ b/file_entity/file_entity.file_api.inc
@@ -0,0 +1,305 @@
+<?php
+
+/**
+ * @file
+ * API extensions of Drupal core's file.inc.
+ */
+
+/**
+ * Returns information about file types from hook_file_type_info().
+ *
+ * @param $file_type
+ *   (optional) A file type name. If ommitted, all file types will be returned.
+ *
+ * @return
+ *   Either a file type description, as provided by hook_file_type_info(), or an
+ *   array of all existing file types, keyed by file type name.
+ */
+function file_info_file_types($file_type = NULL) {
+  $info = &drupal_static(__FUNCTION__);
+  if (!isset($info)) {
+    $info = module_invoke_all('file_type_info');
+    drupal_alter('file_type_info', $info);
+    _file_sort_array_by_weight($info);
+  }
+  if ($file_type) {
+    if (isset($info[$file_type])) {
+      return $info[$file_type];
+    }
+  }
+  else {
+    return $info;
+  }
+}
+
+/**
+ * Determines the file type of a passed in file object.
+ */
+function file_get_type($file) {
+  foreach (file_info_file_types() as $type => $info) {
+    if (isset($info['claim callback']) && ($function = $info['claim callback']) && function_exists($function) && $function($file, $type)) {
+      return $type;
+    }
+  }
+}
+
+/**
+ * Returns information about file formatters from hook_file_formatter_info().
+ *
+ * @param $formatter_type
+ *   (optional) A file formatter type name. If ommitted, all file formatter
+ *   will be returned.
+ *
+ * @return
+ *   Either a file formatter description, as provided by
+ *   hook_file_formatter_info(), or an array of all existing file formatters,
+ *   keyed by formatter type name.
+ */
+function file_info_formatter_types($formatter_type = NULL) {
+  $info = &drupal_static(__FUNCTION__);
+  if (!isset($info)) {
+    $info = module_invoke_all('file_formatter_info');
+    drupal_alter('file_formatter_info', $info);
+    _file_sort_array_by_weight($info);
+  }
+  if ($formatter_type) {
+    if (isset($info[$formatter_type])) {
+      return $info[$formatter_type];
+    }
+  }
+  else {
+    return $info;
+  }
+}
+
+/**
+ * Construct a drupal_render() style array from an array of loaded files.
+ *
+ * @param $files
+ *   An array of files as returned by file_load_multiple().
+ * @param $view_mode
+ *   View mode.
+ * @param $weight
+ *   An integer representing the weight of the first file in the list.
+ * @param $langcode
+ *   A string indicating the language field values are to be shown in. If no
+ *   language is provided the current content language is used.
+ *
+ * @return
+ *   An array in the format expected by drupal_render().
+ */
+function file_view_multiple($files, $view_mode = 'default', $weight = 0, $langcode = NULL) {
+  field_attach_prepare_view('file', $files, $view_mode);
+  entity_prepare_view('file', $files);
+
+  $build = array(
+    '#sorted' => TRUE,
+  );
+  foreach ($files as $file) {
+    $build[$file->fid] = file_view($file, $view_mode, $langcode);
+    $build[$file->fid]['#weight'] = $weight;
+    $weight++;
+  }
+  return $build;
+}
+
+/**
+ * Generate an array for rendering the given file.
+ *
+ * @param $file
+ *   A file object.
+ * @param $view_mode
+ *   View mode.
+ * @param $langcode
+ *   (optional) A language code to use for rendering. Defaults to the global
+ *   content language of the current request.
+ *
+ * @return
+ *   An array as expected by drupal_render().
+ */
+function file_view($file, $view_mode = 'default', $langcode = NULL) {
+  if (!isset($langcode)) {
+    $langcode = $GLOBALS['language_content']->language;
+  }
+
+  // Prepare the file object for viewing, in case file_view() was called by
+  // something other than file_view_multiple(). These functions exit quickly if
+  // they've already run, so it's okay to call them even if they've already been
+  // called by file_view_multiple().
+  field_attach_prepare_view('file', array($file->fid => $file), $view_mode);
+  entity_prepare_view('file', array($file->fid => $file));
+
+  // Create the render array with the file itself and with fields.
+  $build = array(
+    '#file' => $file,
+    '#view_mode' => $view_mode,
+    '#language' => $langcode,
+  );
+  $build += field_attach_view('file', $file, $view_mode, $langcode);
+  $build['file'] = file_view_file($file, $view_mode, $langcode);
+
+  // Allow modules to add and alter.
+  module_invoke_all('file_view', $file, $view_mode, $langcode);
+  module_invoke_all('entity_view', $file, 'file', $view_mode, $langcode);
+  $type = 'file';
+  drupal_alter(array('file_view', 'entity_view'), $build, $type);
+
+  return $build;
+}
+
+/**
+ * Generate an array for rendering just the file portion of a file entity.
+ *
+ * @param $file
+ *   A file object.
+ * @param $displays
+ *   Can be either:
+ *   - the name of a view mode;
+ *   - or an array of custom display settings, as returned by file_displays().
+ * @param $langcode
+ *   (optional) A language code to use for rendering. Defaults to the global
+ *   content language of the current request.
+ *
+ * @return
+ *   An array as expected by drupal_render().
+ */
+function file_view_file($file, $displays = 'default', $langcode = NULL) {
+  if (!isset($langcode)) {
+    $langcode = $GLOBALS['language_content']->language;
+  }
+
+  // Prepare incoming display specifications.
+  if (is_string($displays)) {
+    $view_mode = $displays;
+    $displays = file_displays($file->type, $view_mode);
+  }
+  else {
+    $view_mode = '_custom_display';
+  }
+  drupal_alter('file_displays', $displays, $file, $view_mode);
+  _file_sort_array_by_weight($displays);
+
+  // Attempt to display the file with each of the possible displays. Stop after
+  // the first successful one. See file_displays() for details.
+  $element = NULL;
+  foreach ($displays as $formatter_type => $display) {
+    if (!empty($display['status'])) {
+      $formatter_info = file_info_formatter_types($formatter_type);
+      // Under normal circumstances, the UI prevents enabling formatters for
+      // incompatible file types. In case this was somehow circumvented (for
+      // example, a module updated its formatter definition without updating
+      // existing display settings), perform an extra check here.
+      if (isset($formatter_info['file types']) && !in_array($file->type, $formatter_info['file types'])) {
+        continue;
+      }
+      if (isset($formatter_info['view callback']) && ($function = $formatter_info['view callback']) && function_exists($function)) {
+        $display['type'] = $formatter_type;
+        if (isset($formatter_info['default settings'])) {
+          if (!isset($display['settings'])) {
+            $display['settings'] = array();
+          }
+          $display['settings'] += $formatter_info['default settings'];
+        }
+        $element = $function($file, $display, $langcode);
+        if (isset($element)) {
+          break;
+        }
+      }
+    }
+  }
+
+  // If none of the configured formatters were able to display the file, attempt
+  // to display the file using the file type's default view callback.
+  if (!isset($element)) {
+    $file_type_info = file_info_file_types($file->type);
+    if (isset($file_type_info['default view callback']) && ($function = $file_type_info['default view callback']) && function_exists($function)) {
+      $element = $function($file, $view_mode, $langcode);
+    }
+  }
+
+  // If a render element was returned by a formatter or the file type's default
+  // view callback, add some defaults to it and return it.
+  if (isset($element)) {
+    $element += array(
+      '#file' => $file,
+      '#view_mode' => $view_mode,
+      '#language' => $langcode,
+    );
+    return $element;
+  }
+}
+
+/**
+ * Returns an array of possible displays to use for a file type in a given view mode.
+ *
+ * It is common for a site to be configured with broadly defined file types
+ * (e.g., 'video'), and to have different files of this type require different
+ * displays (for example, the code required to display a YouTube video is
+ * different than the code required to display a local QuickTime video).
+ * Therefore, the site administrator can configure multiple displays for a given
+ * file type. This function returns all of the displays that the administrator
+ * enabled for the given file type in the given view mode. file_view_file() then
+ * invokes each of these, and passes the specific file to display. Each display
+ * implementation can inspect the file, and either return a render array (if it
+ * is capable of displaying the file), or return nothing (if it is incapable of
+ * displaying the file). The first render array returned is the one used.
+ *
+ * @param $file_type
+ *   The type of file.
+ * @param $view_mode
+ *   The view mode.
+ *
+ * @return
+ *   An array keyed by the formatter type name. Each item in the array contains
+ *   the following key/value pairs:
+ *   - status: Whether this display is enabled. If not TRUE, file_view_file()
+ *     skips over it.
+ *   - weight: An integer that determines the order of precedence within the
+ *     returned array. The lowest weight display capable of displaying the file
+ *     is used.
+ *   - settings: An array of key/value pairs specific to the formatter type. See
+ *     hook_file_formatter_info() for details.
+ *
+ * @see hook_file_formatter_info()
+ * @see file_view_file()
+ */
+function file_displays($file_type, $view_mode = 'default') {
+  // If the requested view mode isn't configured to use a custom display for its
+  // fields, then don't use a custom display for its file either.
+  $view_mode_settings = field_view_mode_settings('file', $file_type);
+  if (empty($view_mode_settings[$view_mode]['custom_settings'])) {
+    $view_mode = 'default';
+  }
+
+  $displays_info = variable_get('file_displays');
+  if ($view_mode !== 'default' && isset($displays_info[$file_type][$view_mode])) {
+    $displays = $displays_info[$file_type][$view_mode];
+  }
+  elseif (isset($displays_info[$file_type]['default'])) {
+    $displays = $displays_info[$file_type]['default'];
+  }
+  else {
+    $displays = array();
+  }
+
+  return $displays;
+}
+
+/**
+ * Helper function to sort an array by the value of each item's 'weight' key, while preserving relative order of items that have equal weight.
+ */
+function _file_sort_array_by_weight(&$a) {
+  $i=0;
+  foreach ($a as $key => $item) {
+    if (!isset($a[$key]['weight'])) {
+      $a[$key]['weight'] = 0;
+    }
+    $original_weight[$key] = $a[$key]['weight'];
+    $a[$key]['weight'] += $i/1000;
+    $i++;
+  }
+  uasort($a, 'drupal_sort_weight');
+  foreach ($a as $key => $item) {
+    $a[$key]['weight'] = $original_weight[$key];
+  }
+}
diff --git a/file_entity/file_entity.info b/file_entity/file_entity.info
new file mode 100644
index 0000000..67be8cc
--- /dev/null
+++ b/file_entity/file_entity.info
@@ -0,0 +1,5 @@
+name = "File Entity"
+description = "Extends Drupal file entities to be fieldable and viewable."
+package = Media
+core = 7.x
+dependencies[] = field
diff --git a/file_entity/file_entity.install b/file_entity/file_entity.install
new file mode 100644
index 0000000..3aac9b1
--- /dev/null
+++ b/file_entity/file_entity.install
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ *
+ * Extends the {file_managed} table with a 'type' column.
+ */
+
+/**
+ * Implement hook_schema_alter().
+ */
+function file_entity_schema_alter(&$schema) {
+  $schema['file_managed']['fields']['type'] = array(
+    'description' => 'The type of this file.',
+    'type' => 'varchar',
+    'length' => 50,
+    'not null' => TRUE,
+    'default' => '',
+  );
+  $schema['file_managed']['indexes']['file_type'] = array('type');
+}
+
+/**
+ * Implements hook_install().
+ */
+function file_entity_install() {
+  $schema = array();
+  file_entity_schema_alter($schema);
+  $spec = $schema['file_managed']['fields']['type'];
+  $indexes_new = array('indexes' => $schema['file_managed']['indexes']);
+
+  // If another module (e.g., Media) had added a {file_managed}.type field,
+  // then change it to the expected specification. Otherwise, add the field.
+  if (db_field_exists('file_managed', 'type')) {
+    // db_change_field() will fail if any records have type=NULL, so update
+    // them to the new default value.
+    db_update('file_managed')->fields(array('type' => $spec['default']))->isNull('type')->execute();
+
+    // Indexes using a field being changed must be dropped prior to calling
+    // db_change_field(). However, the database API doesn't provide a way to do
+    // this without knowing what the old indexes are. Therefore, it is the
+    // responsibility of the module that added them to drop them prior to
+    // allowing this module to be installed.
+    db_change_field('file_managed', 'type', 'type', $spec, $indexes_new);
+  }
+  else {
+    db_add_field('file_managed', 'type', $spec, $indexes_new);
+  }
+}
+
+/**
+ * Implement hook_uninstall().
+ */
+function file_entity_uninstall() {
+  db_drop_field('file_managed', 'type');
+}
diff --git a/file_entity/file_entity.module b/file_entity/file_entity.module
new file mode 100644
index 0000000..5ae4fd4
--- /dev/null
+++ b/file_entity/file_entity.module
@@ -0,0 +1,385 @@
+<?php
+
+/**
+ * @file
+ * Extends Drupal file entities to be fieldable and viewable.
+ */
+
+/**
+ * As part of extending Drupal core's file entity API, this module adds some
+ * functions to the 'file' namespace. For organization, those are kept in the
+ * 'file_entity.file_api.inc' file.
+ */
+require_once dirname(__FILE__) . '/file_entity.file_api.inc';
+
+/**
+ * Implements hook_help().
+ */
+function file_entity_help($path, $arg) {
+  switch ($path) {
+    case 'admin/config/media/file-types':
+      $output = '<p>' . t('When a file is uploaded to this website, it is assigned one of the following types, based on what kind of file it is.') . '</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function file_entity_menu() {
+  $items['admin/config/media/file-types'] = array(
+    'title' => 'File types',
+    'description' => 'Manage files used on your site.',
+    'page callback' => 'file_entity_list_types_page',
+    'access arguments' => array('administer site configuration'),
+    'file' => 'file_entity.admin.inc',
+  );
+  $items['admin/config/media/file-types/manage/%'] = array(
+    'title' => 'Manage file types',
+    'description' => 'Manage files used on your site.',
+  );
+
+  // Attach a "Manage file display" tab to each file type in the same way that
+  // Field UI attaches "Manage fields" and "Manage display" tabs. Note that
+  // Field UI does not have to be enabled; we're just using the same IA pattern
+  // here for attaching the "Manage file display" page.
+  $entity_info = entity_get_info('file');
+  foreach ($entity_info['bundles'] as $file_type => $bundle_info) {
+    if (isset($bundle_info['admin'])) {
+      // Get the base path and access.
+      $path = $bundle_info['admin']['path'];
+      $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));
+      $access += array(
+        'access callback' => 'user_access',
+        'access arguments' => array('administer site configuration'),
+      );
+
+      // The file type must be passed to the page callbacks. It might be
+      // configured as a wildcard (multiple file types sharing the same menu
+      // router path).
+      $file_type_argument = isset($bundle_info['admin']['bundle argument']) ? $bundle_info['admin']['bundle argument'] : $file_type;
+
+      // Add the 'Manage file display' tab.
+      $items["$path/file-display"] = array(
+        'title' => 'Manage file display',
+        'page callback' => 'drupal_get_form',
+        'page arguments' => array('file_entity_file_display_form', $file_type_argument, 'default'),
+        'type' => MENU_LOCAL_TASK,
+        'weight' => 3,
+        'file' => 'file_entity.admin.inc',
+      ) + $access;
+
+      // Add a secondary tab for each view mode.
+      $weight = 0;
+      $view_modes = array('default' => array('label' => t('Default'))) + $entity_info['view modes'];
+      foreach ($view_modes as $view_mode => $view_mode_info) {
+        $items["$path/file-display/$view_mode"] = array(
+          'title' => $view_mode_info['label'],
+          'page arguments' => array('file_entity_file_display_form', $file_type_argument, $view_mode),
+          'type' => ($view_mode == 'default' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK),
+          'weight' => ($view_mode == 'default' ? -10 : $weight++),
+          'file' => 'file_entity.admin.inc',
+          // View modes for which the 'custom settings' flag isn't TRUE are
+          // disabled via this access callback. This needs to extend, rather
+          // than override normal $access rules.
+          'access callback' => '_file_entity_view_mode_menu_access',
+          'access arguments' => array_merge(array($file_type_argument, $view_mode, $access['access callback']), $access['access arguments']),
+        );
+      }
+    }
+  }
+  return $items;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function file_entity_theme() {
+  return array(
+    'file_entity_file_type_overview' => array(
+      'variables' => array('label' => NULL, 'description' => NULL),
+      'file' => 'file_entity.admin.inc',
+    ),
+    'file_entity_file_display_order' => array(
+      'render element' => 'element',
+      'file' => 'file_entity.admin.inc',
+    ),
+  );
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ *
+ * Extends the core file entity to be fieldable. Modules can define file types
+ * via hook_file_type_info(). For each defined type, create a bundle, so that
+ * fields can be configured per file type.
+ */
+function file_entity_entity_info_alter(&$entity_info) {
+  $entity_info['file']['fieldable'] = TRUE;
+  $entity_info['file']['entity keys']['bundle'] = 'type';
+  $entity_info['file']['bundles'] = array();
+  foreach (file_info_file_types() as $type => $info) {
+    $info += array(
+      // Provide a default administration path for Field UI, but not if 'admin'
+      // has been explicitly set to NULL.
+      'admin' => array(
+        'path' => 'admin/config/media/file-types/manage/%',
+        'real path' => 'admin/config/media/file-types/manage/' . $type,
+        'bundle argument' => 5,
+      ),
+    );
+    $entity_info['file']['bundles'][$type] = array_intersect_key($info, drupal_map_assoc(array('label', 'admin')));
+  }
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ *
+ * Adds 'file' as an extra field, so that its display and form component can be
+ * weighted relative to the fields that are added to file entity bundles.
+ */
+function file_entity_field_extra_fields() {
+  $return = array();
+  $info = entity_get_info('file');
+  foreach (array_keys($info['bundles']) as $bundle) {
+    $return['file'][$bundle] = array(
+      'form' => array(
+        'file' => array(
+          'label' => t('File'),
+          'description' => t('File preview'),
+          'weight' => 0,
+        ),
+      ),
+      'display' => array(
+        'file' => array(
+          'label' => t('File'),
+          'description' => t('File display'),
+          'weight' => 0,
+        ),
+      ),
+    );
+  }
+
+  return $return;
+}
+
+/**
+ * Implements hook_file_presave().
+ */
+function file_entity_file_presave($file) {
+  // The file type is a bundle key, so can't be NULL. file_entity_schema_alter()
+  // ensures that it isn't NULL after a file_load(). However, file_save() can be
+  // called on a new file object, so we apply the default here as well.
+  if (!isset($file->type)) {
+    $file->type = '';
+  }
+
+  // If the file doesn't already have a real type, attempt to assign it one.
+  if ($file->type === '' && ($type = file_get_type($file))) {
+    $file->type = $type;
+  }
+
+  field_attach_presave('file', $file);
+}
+
+/**
+ * Implements hook_file_insert().
+ */
+function file_entity_file_insert($file) {
+  field_attach_insert('file', $file);
+}
+
+/**
+ * Implement hook_file_update().
+ */
+function file_entity_file_update($file) {
+  field_attach_update('file', $file);
+}
+
+/**
+ * Implements hook_file_delete().
+ */
+function file_entity_file_delete($file) {
+  field_attach_delete('file', $file);
+}
+
+/**
+ * Implements hook_file_formatter_info().
+ */
+function file_entity_file_formatter_info() {
+  $formatters = array();
+
+  // Allow file field formatters to be reused for displaying the file entity's
+  // file pseudo-field.
+  if (module_exists('file')) {
+    foreach (field_info_formatter_types() as $field_formatter_type => $field_formatter_info) {
+      if (in_array('file', $field_formatter_info['field types'])) {
+        $formatters['file_field_' . $field_formatter_type] = array(
+          'label' => $field_formatter_info['label'],
+          'view callback' => 'file_entity_file_formatter_file_field_view',
+        );
+        if (isset($field_formatter_info['settings'])) {
+          $formatters['file_field_' . $field_formatter_type] += array(
+            'default settings' => $field_formatter_info['settings'],
+            'settings callback' => 'file_entity_file_formatter_file_field_settings',
+          );
+        }
+      }
+    }
+  }
+
+  // Add a simple file formatter for displaying an image in a chosen style.
+  if (module_exists('image')) {
+    $formatters['file_image'] = array(
+      'label' => t('Image'),
+      'default settings' => array('image_style' => ''),
+      'view callback' => 'file_entity_file_formatter_file_image_view',
+      'settings callback' => 'file_entity_file_formatter_file_image_settings',
+    );
+  }
+
+  return $formatters;
+}
+
+/**
+ * Implements hook_file_formatter_FORMATTER_view().
+ *
+ * This function provides a bridge to the field formatter API, so that file
+ * field formatters can be reused for displaying the file entity's file
+ * pseudo-field.
+ */
+function file_entity_file_formatter_file_field_view($file, $display, $langcode) {
+  if (strpos($display['type'], 'file_field_') === 0) {
+    $field_formatter_type = substr($display['type'], strlen('file_field_'));
+    $field_formatter_info = field_info_formatter_types($field_formatter_type);
+    if (isset($field_formatter_info['module'])) {
+      // Set $display['type'] to what hook_field_formatter_*() expects.
+      $display['type'] = $field_formatter_type;
+
+      // Set $items to what file field formatters expect. See file_field_load(),
+      // and note that, here, $file is already a fully loaded entity.
+      $items = array((array) $file);
+
+      // Invoke hook_field_formatter_prepare_view() and
+      // hook_field_formatter_view(). Note that we are reusing field formatter
+      // functions, but we are not displaying a Field API field, so we set
+      // $field and $instance accordingly, and do not invoke
+      // hook_field_prepare_view(). This assumes that the formatter functions do
+      // not rely on $field or $instance. A module that implements formatter
+      // functions that rely on $field or $instance (and therefore, can only be
+      // used for real fields) can prevent this formatter from being used on the
+      // pseudo-field by removing it within hook_file_formatter_info_alter().
+      $field = $instance = NULL;
+      if (($function = ($field_formatter_info['module'] . '_field_formatter_prepare_view')) && function_exists($function)) {
+        $fid = $file->fid;
+        // hook_field_formatter_prepare_view() alters $items by reference.
+        $grouped_items = array($fid => &$items);
+        $function('file', array($fid => $file), $field, array($fid => $instance), $langcode, $grouped_items, array($fid => $display));
+      }
+      if (($function = ($field_formatter_info['module'] . '_field_formatter_view')) && function_exists($function)) {
+        $element = $function('file', $file, $field, $instance, $langcode, $items, $display);
+        // We passed the file as $items[0], so return the corresponding element.
+        if (isset($element[0])) {
+          return $element[0];
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_file_formatter_FORMATTER_settings().
+ *
+ * This function provides a bridge to the field formatter API, so that file
+ * field formatters can be reused for displaying the file entity's file
+ * pseudo-field.
+ */
+function file_entity_file_formatter_file_field_settings($form, &$form_state, $settings, $formatter_type, $file_type, $view_mode) {
+  if (strpos($formatter_type, 'file_field_') === 0) {
+    $field_formatter_type = substr($formatter_type, strlen('file_field_'));
+    $field_formatter_info = field_info_formatter_types($field_formatter_type);
+
+    // Invoke hook_field_formatter_settings_form(). We are reusing field
+    // formatter functions, but we are not working with a Field API field, so
+    // set $field accordingly. Unfortunately, the API is for $settings to be
+    // transfered via the $instance parameter, so we must mock it.
+    if (isset($field_formatter_info['module']) && ($function = ($field_formatter_info['module'] . '_field_formatter_settings_form')) && function_exists($function)) {
+      $field = NULL;
+      $mock_instance['display'][$view_mode]['settings'] = $settings;
+      return $function($field, $mock_instance, $view_mode, $form, $form_state);
+    }
+  }
+}
+
+/**
+ * Implements hook_file_formatter_FORMATTER_view().
+ *
+ * Returns a drupal_render() array to display an image of the chosen style.
+ *
+ * This formatter is only capable of displaying local images. If the passed in
+ * file is either not local or not an image, nothing is returned, so that
+ * file_view_file() can try another formatter.
+ */
+function file_entity_file_formatter_file_image_view($file, $display, $langcode) {
+  $scheme = file_uri_scheme($file->uri);
+  $local_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
+  if (isset($local_wrappers[$scheme]) && strpos($file->filemime, 'image/') === 0) {
+    if (!empty($display['settings']['image_style'])) {
+      $element = array(
+        '#theme' => 'image_style',
+        '#style_name' => $display['settings']['image_style'],
+        '#path' => $file->uri,
+      );
+    }
+    else {
+      $element = array(
+        '#theme' => 'image',
+        '#path' => $file->uri,
+      );
+    }
+    return $element;
+  }
+}
+
+/**
+ * Implements hook_file_formatter_FORMATTER_settings().
+ *
+ * Returns form elements for configuring the 'file_image' formatter.
+ */
+function file_entity_file_formatter_file_image_settings($form, &$form_state, $settings) {
+  $element = array();
+  $element['image_style'] = array(
+    '#title' => t('Image style'),
+    '#type' => 'select',
+    '#options' => image_style_options(FALSE),
+    '#default_value' => $settings['image_style'],
+    '#empty_option' => t('None (original image)'),
+  );
+  return $element;
+}
+
+/**
+ * Menu access callback for the 'view mode file display settings' pages.
+ *
+ * Based on _field_ui_view_mode_menu_access(), but the Field UI module might not
+ * be enabled.
+ */
+function _file_entity_view_mode_menu_access($file_type, $view_mode, $access_callback) {
+  // Deny access if the view mode isn't configured to use custom display
+  // settings.
+  $view_mode_settings = field_view_mode_settings('file', $file_type);
+  $visibility = ($view_mode == 'default') || !empty($view_mode_settings[$view_mode]['custom_settings']);
+  if (!$visibility) {
+    return FALSE;
+  }
+
+  // Otherwise, continue to an $access_callback check.
+  $args = array_slice(func_get_args(), 3);
+  $callback = empty($access_callback) ? 0 : trim($access_callback);
+  if (is_numeric($callback)) {
+    return (bool) $callback;
+  }
+  elseif (function_exists($access_callback)) {
+    return call_user_func_array($access_callback, $args);
+  }
+}
diff --git a/file_entity/tests/file_entity_test.info b/file_entity/tests/file_entity_test.info
new file mode 100644
index 0000000..1984c57
--- /dev/null
+++ b/file_entity/tests/file_entity_test.info
@@ -0,0 +1,6 @@
+name = "File Entity Test"
+description = "Support module for File Entity tests."
+package = Testing
+core = 7.x
+dependencies[] = file_entity
+;hidden=TRUE
diff --git a/file_entity/tests/file_entity_test.module b/file_entity/tests/file_entity_test.module
new file mode 100644
index 0000000..7879703
--- /dev/null
+++ b/file_entity/tests/file_entity_test.module
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * File Entity Test
+ */
+
+
+/**
+ * Implements hook_menu().
+ */
+function file_entity_test_menu() {
+  $items = array();
+
+  $items['file-entity-test/file/add'] = array(
+    'title' => 'Add file',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('file_entity_test_add_form'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'file_entity_test.pages.inc',
+  );
+  $items['file-entity-test/file/%file'] = array(
+    'title' => 'View file',
+    'page callback' => 'file_entity_test_view_page',
+    'page arguments' => array(2),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'file_entity_test.pages.inc',
+  );
+  $items['file-entity-test/file/%file/view'] = array(
+    'title' => 'View',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['file-entity-test/file/%file/preview'] = array(
+    'title' => 'Preview',
+    'page callback' => 'file_entity_test_preview_page',
+    'page arguments' => array(2),
+    'access arguments' => array('administer site configuration'),
+    'weight' => 0,
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'file_entity_test.pages.inc',
+  );
+  $items['file-entity-test/file/%file/edit'] = array(
+    'title' => 'Edit',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('file_entity_test_edit_form', 2),
+    'access arguments' => array('administer site configuration'),
+    'weight' => 1,
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'file_entity_test.pages.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Implements hook_file_type_info().
+ */
+function file_entity_test_file_type_info() {
+  return array(
+    'file_entity_test' => array(
+      'label' => t('Test'),
+      'description' => t('A file type defined by the File Entity Test module. Used for testing only.'),
+      'claim callback' => 'file_entity_test_file_type_file_entity_test_claim',
+      'default view callback' => 'file_entity_test_file_type_file_entity_test_default_view',
+      'weight' => 100,
+    ),
+  );
+}
+
+/**
+ * Implements hook_file_type_TYPE_claim().
+ *
+ * Returns TRUE if the passed in file should be assigned the 'file_entity_test'
+ * file type.
+ */
+function file_entity_test_file_type_file_entity_test_claim($file) {
+  return TRUE;
+}
+
+/**
+ * Implements hook_file_type_TYPE_default_view().
+ */
+function file_entity_test_file_type_file_entity_test_default_view($file, $view_mode, $langcode) {
+  return array(
+    '#type' => 'link',
+    '#title' => $file->filename,
+    '#href' => file_create_url($file->uri),
+  );
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function file_entity_test_entity_info_alter(&$entity_info) {
+  $entity_info['file']['view modes']['file_entity_test_preview'] = array(
+    'label' => t('Test Preview'),
+    'custom settings' => TRUE,
+  );
+}
diff --git a/file_entity/tests/file_entity_test.pages.inc b/file_entity/tests/file_entity_test.pages.inc
new file mode 100644
index 0000000..474543a
--- /dev/null
+++ b/file_entity/tests/file_entity_test.pages.inc
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @file
+ * Test pages for the File Entity Test module.
+ */
+
+/**
+ * Form callback; upload a file.
+ */
+function file_entity_test_add_form($form, &$form_state) {
+  $form['file'] = array(
+    '#type' => 'managed_file',
+    '#required' => TRUE,
+    '#title' => 'File',
+    '#upload_location' => 'public://',
+  );
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  return $form;
+}
+
+/**
+ * Form submit callback; save the uploaded file.
+ */
+function file_entity_test_add_form_submit($form, &$form_state) {
+  $file = file_load($form_state['values']['file']);
+  if (!$file->status) {
+    $file->status = FILE_STATUS_PERMANENT;
+    file_save($file);
+  }
+  drupal_set_message(t('Your file has been saved.'));
+  $form_state['redirect'] = 'file-entity-test/file/' . $file->fid;
+}
+
+/**
+ * Page callback; view a file.
+ */
+function file_entity_test_view_page($file) {
+  return file_view($file, 'default');
+}
+
+/**
+ * Page callback; preview a file.
+ */
+function file_entity_test_preview_page($file) {
+  return file_view($file, 'file_entity_test_preview');
+}
+
+/**
+ * Form callback; edit a file.
+ */
+function file_entity_test_edit_form($form, &$form_state, $file) {
+  $form_state['file'] = $file;
+  field_attach_form('file', $file, $form, $form_state);
+  $form['file'] = file_view($file, 'file_entity_test_preview');
+
+  // Add internal file properties needed by
+  // file_entity_test_edit_form_validate().
+  foreach (array('fid', 'type') as $key) {
+    $form[$key] = array('#type' => 'value', '#value' => $file->$key);
+  }
+
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  return $form;
+}
+
+/**
+ * Form validation handler for the file edit form.
+ */
+function file_entity_test_edit_form_validate($form, &$form_state) {
+  entity_form_field_validate('file', $form, $form_state);
+}
+
+/**
+ * Form submit handler for the file edit form
+ */
+function file_entity_test_edit_form_submit($form, &$form_state) {
+  $file = $form_state['file'];
+  entity_form_submit_build_entity('file', $file, $form, $form_state);
+  file_save($file);
+  drupal_set_message(t('Your changes to the file have been saved.'));
+  $form_state['redirect'] = 'file-entity-test/file/' . $file->fid;
+}
diff --git a/includes/MediaEntityController.php b/includes/MediaEntityController.php
deleted file mode 100644
index 6718a1a..0000000
--- a/includes/MediaEntityController.php
+++ /dev/null
@@ -1,210 +0,0 @@
-<?php
-
-/**
- * Extends DrupalEntityControllerInterface.
- *
- * @see: http://drupal.org/project/entity_api (perhaps we should be using this)
- */
-class MediaEntityController extends DrupalDefaultEntityController {
-
-  public function load($ids = array(), $conditions = array()) {
-    $this->ids = $ids;
-    $this->conditions = $conditions;
-
-    $entities = array();
-
-    // Revisions are not statically cached, and require a different query to
-    // other conditions, so separate the revision id into its own variable.
-    if ($this->revisionKey && isset($this->conditions[$this->revisionKey])) {
-      $this->revisionId = $this->conditions[$this->revisionKey];
-      unset($this->conditions[$this->revisionKey]);
-    }
-    else {
-      $this->revisionId = FALSE;
-    }
-
-    // Create a new variable which is either a prepared version of the $ids
-    // array for later comparison with the entity cache, or FALSE if no $ids
-    // were passed. The $ids array is reduced as items are loaded from cache,
-    // and we need to know if it's empty for this reason to avoid querying the
-    // database when all requested entities are loaded from cache.
-    $passed_ids = !empty($this->ids) ? array_flip($this->ids) : FALSE;
-    // Try to load entities from the static cache, if the entity type supports
-    // static caching.
-    if ($this->cache) {
-      $entities += $this->cacheGet($this->ids, $this->conditions);
-      // If any entities were loaded, remove them from the ids still to load.
-      if ($passed_ids) {
-        $this->ids = array_keys(array_diff_key($passed_ids, $entities));
-      }
-    }
-
-    // Load any remaining entities from the database. This is the case if $ids
-    // is set to FALSE (so we load all entities), if there are any ids left to
-    // load, if loading a revision, or if $conditions was passed without $ids.
-    if ($this->ids === FALSE || $this->ids || $this->revisionId || ($this->conditions && !$passed_ids)) {
-      // Build the query.
-      $this->buildQuery();
-      $queried_entities = $this->query
-        ->execute()
-        ->fetchAllAssoc($this->idKey);
-    }
-
-    // Pass all entities loaded from the database through $this->attachLoad(),
-    // which attaches fields (if supported by the entity type) and calls the
-    // entity type specific load callback, for example hook_node_load().
-    if (!empty($queried_entities)) {
-      $this->attachLoad($queried_entities);
-      $entities += $queried_entities;
-    }
-
-    if ($this->cache) {
-      // Add entities to the cache if we are not loading a revision.
-      if (!empty($queried_entities) && !$this->revisionId) {
-        $this->cacheSet($queried_entities);
-      }
-    }
-
-    // Ensure that the returned array is ordered the same as the original
-    // $ids array if this was passed in and remove any invalid ids.
-    if ($passed_ids) {
-      // Remove any invalid ids from the array.
-      $passed_ids = array_intersect_key($passed_ids, $entities);
-      foreach ($entities as $entity) {
-        $passed_ids[$entity->{$this->idKey}] = $entity;
-      }
-      $entities = $passed_ids;
-    }
-
-    return $entities;
-  }
-  protected function buildQuery() {
-    $this->query = db_select($this->entityInfo['base table'], 'base');
-
-    $this->query->addTag($this->entityType . '_load_multiple');
-
-    if ($this->revisionId) {
-      $this->query->join($this->revisionTable, 'revision', "revision.{$this->idKey} = base.{$this->idKey} AND revision.{$this->revisionKey} = :revisionId", array(':revisionId' => $this->revisionId));
-    }
-    elseif ($this->revisionKey) {
-      $this->query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}");
-    }
-
-    // Add fields from the {entity} table.
-    $entity_fields = drupal_schema_fields_sql($this->entityInfo['base table']);
-
-    if ($this->revisionKey) {
-      // Add all fields from the {entity_revision} table.
-      $entity_revision_fields = drupal_map_assoc(drupal_schema_fields_sql($this->revisionTable));
-      // The id field is provided by entity, so remove it.
-      unset($entity_revision_fields[$this->idKey]);
-
-      // Change timestamp to revision_timestamp, and revision uid to
-      // revision_uid before adding them to the query.
-      // TODO: This is node specific and has to be moved into NodeController.
-      unset($entity_revision_fields['timestamp']);
-      $this->query->addField('revision', 'timestamp', 'revision_timestamp');
-      unset($entity_revision_fields['uid']);
-      $this->query->addField('revision', 'uid', 'revision_uid');
-
-      // Remove all fields from the base table that are also fields by the same
-      // name in the revision table.
-      $entity_field_keys = array_flip($entity_fields);
-      foreach ($entity_revision_fields as $key => $name) {
-        if (isset($entity_field_keys[$name])) {
-          unset($entity_fields[$entity_field_keys[$name]]);
-        }
-      }
-      $this->query->fields('revision', $entity_revision_fields);
-    }
-
-    $this->query->fields('base', $entity_fields);
-
-    if ($this->ids) {
-      $this->query->condition("base.{$this->idKey}", $this->ids, 'IN');
-    }
-    if ($this->conditions) {
-      foreach ($this->conditions as $field => $value) {
-        $this->query->condition('base.' . $field, $value);
-      }
-    }
-  }
-
-  /**
-   * Attach data to entities upon loading.
-   *
-   * This will attach fields, if the entity is fieldable. It calls
-   * hook_entity_load() for modules which need to add data to all entities.
-   * It also calls hook_TYPE_load() on the loaded entities. For example
-   * hook_node_load() or hook_user_load(). If your hook_TYPE_load()
-   * expects special parameters apart from the queried entities, you can set
-   * $this->hookLoadArguments prior to calling the method.
-   * See NodeController::attachLoad() for an example.
-   */
-  protected function attachLoad(&$queried_entities) {
-    // Attach fields.
-    if ($this->entityInfo['fieldable']) {
-      if ($this->revisionId) {
-        field_attach_load_revision($this->entityType, $queried_entities);
-      }
-      else {
-        field_attach_load($this->entityType, $queried_entities);
-      }
-    }
-
-    // Call hook_entity_load().
-    foreach (module_implements('entity_load') as $module) {
-      $function = $module . '_entity_load';
-      $function($queried_entities, $this->entityType);
-    }
-    // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
-    // always the queried entities, followed by additional arguments set in
-    // $this->hookLoadArguments.
-    $args = array_merge(array($queried_entities), $this->hookLoadArguments);
-    foreach (module_implements($this->entityInfo['load hook']) as $module) {
-      call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args);
-    }
-  }
-
-  /**
-   * Get entities from the static cache.
-   *
-   * @param $ids
-   *   If not empty, return entities that match these IDs.
-   * @param $conditions
-   *   If set, return entities that match all of these conditions.
-   */
-  protected function cacheGet($ids, $conditions = array()) {
-    $entities = array();
-    // Load any available entities from the internal cache.
-    if (!empty($this->entityCache) && !$this->revisionId) {
-      if ($ids) {
-        $entities += array_intersect_key($this->entityCache, array_flip($ids));
-      }
-      // If loading entities only by conditions, fetch all available entities
-      // from the cache. Entities which don't match are removed later.
-      elseif ($conditions) {
-        $entities = $this->entityCache;
-      }
-    }
-
-    // Exclude any entities loaded from cache if they don't match $conditions.
-    // This ensures the same behavior whether loading from memory or database.
-    if ($conditions) {
-      foreach ($entities as $entity) {
-        $entity_values = (array) $entity;
-        if (array_diff_assoc($conditions, $entity_values)) {
-          unset($entities[$entity->{$this->idKey}]);
-        }
-      }
-    }
-    return $entities;
-  }
-
-  /**
-   * Store entities in the static entity cache.
-   */
-  protected function cacheSet($entities) {
-    $this->entityCache += $entities;
-  }
-}
diff --git a/includes/media.admin.inc b/includes/media.admin.inc
index 5708e39..6e1f2cf 100644
--- a/includes/media.admin.inc
+++ b/includes/media.admin.inc
@@ -156,20 +156,13 @@ function media_admin_thumbnails() {
   //   with browser_pager_limit?
   $limit = variable_get('media_admin_limit', 50);
 
-  $query = new EntityFieldQuery();
 
+  $query = new EntityFieldQuery();
   $query->pager($limit);
-  $query->entityCondition('entity_type', 'media');
+  $query->entityCondition('entity_type', 'file');
   $query->propertyCondition('status', FILE_STATUS_PERMANENT);
-
   $result = $query->execute();
-  $ids = key_exists('media', $result) ? array_keys($result['media']) : FALSE;
-  if ($ids) {
-    $media_entities = entity_load('media', $ids);
-  }
-  else {
-    $media_entities = array();
-  }
+  $files = file_load_multiple(array_keys($result['file']));
 
   $destination = drupal_get_destination();
   $rows = array();
@@ -180,16 +173,14 @@ function media_admin_thumbnails() {
    '#suffix' => '</ul></div>',
   );
 
-  if (count($media_entities)) {
-    foreach ($media_entities as $media) {
-     $preview = media_get_thumbnail_preview($media, TRUE);
-     $form['files'][$media->fid] = array(
-       '#type' => 'checkbox',
-       '#title' => '',
-       '#prefix' => '<li>' . drupal_render($preview),
-       '#suffix' => '</li>',
-     );
-    }
+  foreach ($files as $file) {
+   $preview = media_get_thumbnail_preview($file, TRUE);
+   $form['files'][$file->fid] = array(
+     '#type' => 'checkbox',
+     '#title' => '',
+     '#prefix' => '<li>' . drupal_render($preview),
+     '#suffix' => '</li>',
+   );
   }
 
   $form['pager'] = array('#markup' => theme('pager', array('tags' => NULL)));
@@ -287,56 +278,6 @@ function media_admin_submit($form, &$form_state) {
 }
 
 /**
- * Display all media types.
- */
-function media_admin_type_list() {
-  $types = media_type_get_types();
-  $field_ui = module_exists('field_ui');
-  $header = array(t('Name'), array('data' => t('Operations'), 'colspan' => $field_ui ? '4' : '2'));
-  $rows = array();
-
-  foreach ($types as $key => $info) {
-    $type = $types[$key];
-    $type_url_str = str_replace('_', '-', $key);
-    $row = array();
-    $label = $info->label;
-
-    // Set the name column.
-    $row[] = array('data' => t($info->label));
-
-    // Set the edit column.
-    $row[] = array('data' => l(t('settings'), 'admin/config/media/types/manage/' . $type_url_str));
-
-    if ($field_ui) {
-      // Manage fields.
-      $row[] = array('data' => l(t('manage fields'), 'admin/config/media/types/manage/' . $type_url_str . '/fields'));
-
-      // Display fields.
-      $row[] = array('data' => l(t('manage display'), 'admin/config/media/types/manage/' . $type_url_str . '/display'));
-    }
-
-    // Set the delete column.
-    if (empty($type->base)) {
-      $row[] = array('data' => l(t('delete'), 'admin/config/media/types/manage/' . $type_url_str . '/delete'));
-    }
-    else {
-      $row[] = array('data' => '');
-    }
-
-    $rows[] = $row;
-
-  }
-
-  $build['media_type_table'] = array(
-    '#theme' => 'table',
-    '#header' => $header,
-    '#rows' => $rows
-  );
-
-  return $build;
-}
-
-/**
  *  The administration form for managing media types.
  */
 function media_admin_type_manage_form($form, &$form_state, $media_type) {
@@ -559,8 +500,10 @@ function media_import_batch_import_files($files, &$context) {
       $file_obj = media_parse_to_file($file);
       $context['results']['success'][] = $file;
       if (!$image_in_message) {
-        $media = media_load($file_obj->fid);
-        $image_in_message = field_view_field('media', $media, 'file', 'media_preview');
+        // @todo Is this load step really necessary? When there's time, test
+        //   this, and either remove it, or comment why it's needed.
+        $loaded_file = file_load($file_obj->fid);
+        $image_in_message = file_view_file($loaded_file, 'media_preview');
       }
     }
     catch(Exception $e) {
diff --git a/includes/media.browser.inc b/includes/media.browser.inc
index 108c32c..811df7b 100644
--- a/includes/media.browser.inc
+++ b/includes/media.browser.inc
@@ -12,23 +12,19 @@ function media_browser($selected = NULL) {
   array_walk_recursive($params, '_media_recursive_check_plain');
   media_set_browser_params($params);
   
-  // How do we validate these?  I don't know.
-  // I think PDO should protect them, but I'm not 100% certain.
+  // If one or more files have been selected, the browser interaction is now
+  // complete. Return empty page content to the dialog which now needs to close,
+  // but populate Drupal.settings with information about the selected files.
   if (isset($params['fid'])) {
-    // A form has submitted, selected a piece (or pieces) of media and we refreshed.
-    // So we're just going to send the media entity out as a JS setting so the browser can close itself.
-    $selected = $params['fid'];
-    if (!is_array($params['fid'])) {
-      $selected = array($params['fid']);
-    }
-    if (!is_numeric($selected[0])) {
+    $fids = is_array($params['fid']) ? $params['fid'] : array($params['fid']);
+    if (!is_numeric($fids[0])) {
       throw new Exception('Error selecting media, fid param is not an fid or an array of fids');
     }
-    $selectedMedia = entity_load('media', $selected);
-    foreach ($selectedMedia as &$media) {
-      media_browser_build_media_item($media);
+    $files = file_load_multiple($fids);
+    foreach ($files as $file) {
+      media_browser_build_media_item($file);
     }
-    $setting = array('media' => array('selectedMedia' => array_values($selectedMedia)));
+    $setting = array('media' => array('selectedMedia' => array_values($files)));
     drupal_add_js($setting, 'setting');
     return $output;
   }
@@ -149,12 +145,10 @@ function media_browser_list() {
   $start = isset($params['start']) ? $params['start'] : 0;
   $limit = isset($params['limit']) ? $params['limit'] : media_variable_get('browser_pager_limit');
   
-  $conditions = array();
-
   $query = new EntityFieldQuery();
   
   $query->range($start, $limit);
-  $query->entityCondition('entity_type', 'media');
+  $query->entityCondition('entity_type', 'file');
   $query->entityOrderBy('entity_id', 'DESC');
 
   if ($types) {
@@ -171,7 +165,7 @@ function media_browser_list() {
 
   // @todo Implement granular editorial access: http://drupal.org/node/696970.
   //   In the meantime, protect information about private files from being
-  //   discovered by unprivileged users. See also media_file_view().
+  //   discovered by unprivileged users. See also media_view_page().
   if (!user_access('administer media')) {
     $query->propertyCondition('uri', 'private://%', 'NOT LIKE');
   }
@@ -179,19 +173,12 @@ function media_browser_list() {
   $query->propertyCondition('status', FILE_STATUS_PERMANENT);
 
   $result = $query->execute();
-  $ids = array_keys($result['media']);
-  if ($ids) {
-    $media_entities = entity_load('media', $ids);
-  }
-  else {
-    $media_entities = array();
-  }
-  
-  foreach ($media_entities as &$media) {
-    media_browser_build_media_item($media);
+  $files = file_load_multiple(array_keys($result['file']));
+  foreach ($files as $file) {
+    media_browser_build_media_item($file);
   }
   
-  drupal_json_output(array('media' => array_values($media_entities)));
+  drupal_json_output(array('media' => array_values($files)));
   exit();
 
 }
@@ -415,13 +402,10 @@ EOF;
 }
 
 /**
- * Adds on properties to the media item which are expected by callers who are
- * going to act on the returned item.
- *
- * @param Object &$media;
+ * Adds properties to the passed in file that are needed by the media browser JS code.
  */
-function media_browser_build_media_item(&$media) {
-  $preview = media_get_thumbnail_preview($media);
-  $media->preview = drupal_render($preview);
-  $media->url = file_create_url($media->uri);
+function media_browser_build_media_item($file) {
+  $preview = media_get_thumbnail_preview($file);
+  $file->preview = drupal_render($preview);
+  $file->url = file_create_url($file->uri);
 }
diff --git a/includes/media.fields.inc b/includes/media.fields.inc
index de28823..927eec7 100644
--- a/includes/media.fields.inc
+++ b/includes/media.fields.inc
@@ -57,48 +57,35 @@ function media_field_widget_info() {
 function media_field_formatter_info() {
   $formatters = array();
 
-  // Formatters for media are the same as build modes for media.
-  $build_modes = media_field_view_modes('media');
-  foreach ($build_modes as $key => $mode) {
-    $formatters[$key] = array(
-      'label' => t($mode['label']),
-      'field types' => array('media'),
-    );
+  // Add a media field formatter for each file entity view mode.
+  $entity_info = entity_get_info('file');
+  foreach ($entity_info['view modes'] as $view_mode => $view_mode_info) {
+    // @todo Modules other than Media can also add view modes to file entities.
+    //   No reason not to allow them to be chosen when formatting a Media field,
+    //   but because Field API keeps all formatters across all field types in a
+    //   single array, we run the risk of name collission. The best way to solve
+    //   this is to convert this into a single formatter with a setting for the
+    //   view mode, similar to how the "Image" formatter has a setting for the
+    //   "Image Style".
+    if (strpos($view_mode, 'media_') === 0) {
+      $formatters[$view_mode] = array(
+        'label' => t($view_mode_info['label']),
+        'field types' => array('media'),
+      );
+    }
   }
 
-  // Also add a formatter for files to show larger icons based on mimetype
+  // Also add a formatter for files to show larger icons based on mimetype. This
+  // was originally used when media entities contained file fields. The current
+  // file entity architecture no longer needs this, but people may have used
+  // this formatter for other file fields on their website.
+  // @todo Some day, remove this.
   $formatters['media_large_icon'] = array(
     'label' => t('Large filetype icon'),
     'field types' => array('file'),
   );
-  
-  return $formatters;
-}
-
-/**
- * Used to Implement hook_field_view_modes().
- */
-function media_field_view_modes($obj_type) {
-  $modes = array();
-  if ($obj_type == 'media') {
-    $modes = array(
-      'media_link' => array('label' => t('Link'), 'custom settings' => FALSE),
-      'media_preview' => array('label' => t('Preview'), 'custom settings' => FALSE),
-      'media_small' => array('label' => t('Small'), 'custom settings' => FALSE),
-      'media_large' => array('label' => t('Large'), 'custom settings' => FALSE),
-      'media_original' => array('label' => t('Original'), 'custom settings' => FALSE),
-    );
-  }
-  return $modes;
-}
 
-function media_field_ui_view_modes_tabs() {
-  $modes = array(
-    'basic' => array(
-      'view modes' => array_keys(media_field_view_modes('media')),
-    ),
-  );
-  return $modes;
+  return $formatters;
 }
 
 /**
@@ -108,7 +95,7 @@ function media_field_ui_view_modes_tabs() {
  *   hook to load referenced entities.
  */
 function media_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
-  // Collect all media IDs that need loading.
+  // Collect all file ids that need loading.
   $fids = array();
   foreach ($entities as $id => $entity) {
     foreach ($items[$id] as $delta => $item) {
@@ -118,31 +105,30 @@ function media_field_prepare_view($entity_type, $entities, $field, $instances, $
     }
   }
 
-  // Load the entities in a single operation.
-  $media_entities = media_load_multiple(array_values($fids));
+  // Load them.
+  $files = file_load_multiple(array_values($fids));
 
-  // Populate that item data with the loaded entities.
+  // Replace the item data with loaded files instead of just the file ids.
+  // @todo This causes problems with some Field API and other module
+  //   integration. It would be better for the loaded file to be added into
+  //   $item, not replace it. See http://drupal.org/node/1005642.
   foreach ($entities as $id => $entity) {
     foreach ($items[$id] as $delta => $item) {
       if (is_array($item) && !empty($item['fid'])) {
-        $items[$id][$delta] = $media_entities[$item['fid']];
+        $items[$id][$delta] = $files[$item['fid']];
       }
     }
   }
 }
 
 /**
- * Implement hook_field_formatter_view
+ * Implement hook_field_formatter_view().
  */
 function media_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
-  
-  $element = array();
-  
-  // This block handles fallback file type icons for media which doesn't have
-  // a thumbnail.  It's kind of a hack, and belongs in the file module.
+  // @todo Legacy support for the extra formatter added to file fields. See
+  //   media_field_formatter_info().
   if ($display['type'] == 'media_large_icon') {
     foreach ($items as $delta => $item) {
-      // Display all values in a single element..
       $element[$delta] = array(
         '#theme' => 'media_formatter_large_icon',
         '#file' => $item,
@@ -150,48 +136,14 @@ function media_field_formatter_view($entity_type, $entity, $field, $instance, $l
     }
     return $element;
   }
-  
-  // The rest of this hook invocation is for rendering media when it is attached
-  // as a field to other entities.  The formatter on the media field is equivilent
-  // to the media view_mode.  So if someone configures a media field on a node
-  // and says that the teaser formatter for the media is "large", it will render
-  // the media entity as per the large view_mode.
-
-  // An array of media items to pass into entity rendering functions.
-  $media_items = array();
 
-  foreach ($items as $delta => $item) {
-    $media_items[$item->fid] = $item;
-    //$media_items[$item['fid']] = media_load($item['fid']);
+  // @todo media_field_prepare_view() ensures that $items is an array of loaded
+  //   files, but that will need to change. See that function for details.
+  $files = array();
+  foreach ($items as $file) {
+    $files[$file->fid] = $file;
   }
-
-  field_attach_prepare_view('media', $media_items, $display['type']);
-  entity_prepare_view('media', $media_items);
-
-
-
-  foreach ($items as $delta => $item) {
-    // @TODO:
-    // This works fine for Media styles, just not the default file types.
-    // Find out why this doesn't automatically already route through file.
-    // We're using a filefield in theory, created during the media_type
-    // instantiation at media_type_configure_fields().
-    // Other fields are routed properly, but not that default field...
-
-    // First check if the default File formatters are sufficient.
-//     $element[$delta] = file_field_formatter_view($obj_type, $object, $field, $instance, $langcode, $items, $display);
-
-    // If this is not handled by the File module, then defer to Styles.
-//     if (!$element[$delta]) {
-      $element[$delta] = field_attach_view('media', $item, $display['type'], $langcode);
-
-      // We'll defer to the Styles module to route formatting for the 'file'.
-      $element[$delta]['file']['#theme'] = 'styles_field_formatter';
-      $element[$delta]['file']['#element'] = $element[$delta]['file'];
-//     }
-  }
-
-  return $element;
+  return file_view_multiple($files, $display['type'], 0, $langcode);
 }
 
 /**
diff --git a/includes/media.filter.inc b/includes/media.filter.inc
index 0930699..c51c4cc 100644
--- a/includes/media.filter.inc
+++ b/includes/media.filter.inc
@@ -76,20 +76,14 @@ function _media_url_parse_full_links($match) {
   }
 
   if ($file->fid) {
-    // Return the embedded media.
-    // Now load the desired media to display.
-    $medias = entity_load('media', array($file->fid));
+    $file = file_load($file->fid);
 
-    if (!empty($medias)) {
-      $media = array_pop($medias);
+    // Generate a preview of the file
+    // @TODO: Allow user to change the formatter in the filter settings.
+    $preview = file_view_file($file, 'media_large');
+    $preview['#show_names'] = TRUE;
 
-      // Generate a preview of the file
-      // @TODO: Allow user to change the formatter in the filter settings.
-      $preview = field_view_field('media', $media, 'file', 'media_large');
-      $preview['#show_names'] = TRUE;
-
-      return drupal_render($preview);
-    }
+    return drupal_render($preview);
   }
 
   // Nothing was parsed; return the original text.
@@ -121,6 +115,7 @@ function _media_url_curry($func, $arity) {
  * Return the replaced markup
  */
 function media_token_to_markup($match, $wysiwyg = FALSE) {
+  $settings = array();
   $match = str_replace("[[","",$match);
   $match = str_replace("]]","",$match);
   $tag = $match[0];
@@ -130,33 +125,33 @@ function media_token_to_markup($match, $wysiwyg = FALSE) {
       throw new Exception('Unable to find matching tag');
     }
     
-    $media = drupal_json_decode($tag);
+    $tag_info = drupal_json_decode($tag);
 
-    if (!isset($media['fid'])) {
+    if (!isset($tag_info['fid'])) {
       throw new Exception('No file Id');
     }
-    if (!isset($media['view_mode'])) {
+    if (!isset($tag_info['view_mode'])) {
       // Should we log or throw an exception here instead?
       // Do we need to validate the view mode for fields API?
-      $media['view_mode'] = media_variable_get('wysiwyg_default_view_mode');
+      $tag_info['view_mode'] = media_variable_get('wysiwyg_default_view_mode');
     }
-    $media_obj = media_load($media['fid']);
-    if (!$media_obj) {
+    $file = file_load($tag_info['fid']);
+    if (!$file) {
      throw new Exception('Could not load media object');
     }
 
-    // Track the fid of this media object in the {media_filter_usage} table.
-    media_filter_track_usage($media['fid']);
+    // Track the fid of this file in the {media_filter_usage} table.
+    media_filter_track_usage($file->fid);
 
-    $settings = is_array($media['attributes']) ? $media['attributes'] : array();
+    $attributes = is_array($tag_info['attributes']) ? $tag_info['attributes'] : array();
     $attribute_whitelist = media_variable_get('wysiwyg_allowed_attributes');
-    $settings = array_intersect_key($settings, array_flip($attribute_whitelist));
+    $settings['attributes'] = array_intersect_key($attributes, array_flip($attribute_whitelist));
 
     // @TODO: What case does this provide for?  Can we add this logic in JS when we embed it?
     // This doesn't look great to me.  Also won't work if the style has anything
     // between width and height (or if they are in reverse order).
-    if (isset($settings['style'])) {
-      if (preg_match('@width: (.+?)px; height: (.+?)px;@i', $settings['style'], $matches)) {
+    if (isset($settings['attributes']['style'])) {
+      if (preg_match('@width: (.+?)px; height: (.+?)px;@i', $settings['attributes']['style'], $matches)) {
         $settings['width'] = $matches[1];
         $settings['height'] = $matches[2];
       }
@@ -170,8 +165,8 @@ function media_token_to_markup($match, $wysiwyg = FALSE) {
     return '';
   }
 
-  $file_field = media_get_file_without_label($media_obj, $media['view_mode'], $settings);
-  return drupal_render($file_field);
+  $element = media_get_file_without_label($file, $tag_info['view_mode'], $settings);
+  return drupal_render($element);
 }
 
 /**
@@ -246,32 +241,29 @@ function _media_generate_tagMap($text) {
  * Allows the user to pick a format for their media file.
  * Can also have additional params depending on the media type.
  */
-function media_format_form($form, $form_state, $media) {
-  // This will vary depending on the media type.
+function media_format_form($form, $form_state, $file) {
   $form = array();
-  $form['#media'] = $media;
+  $form['#media'] = $file;
 
-  $instance_info = field_info_instance('media', 'file', $media->type);
-  $entity_info = entity_get_info('media');
+  $entity_info = entity_get_info('file');
   $view_modes = $entity_info['view modes'];
+  drupal_alter('media_wysiwyg_allowed_view_modes', $view_modes, $file);
 
   $options = array();
-
-  drupal_alter('media_wysiwyg_allowed_view_modes', $media_type, $view_modes);
-
-  foreach ($view_modes as $key => $title) {
-    $format = field_get_display($instance_info, $key, $media);
-    //If the format is set to hidden, don't offer it
-    if ($format['type'] == 'hidden') {
+  foreach ($view_modes as $view_mode => $view_mode_info) {
+    // Don't present the user with an option to choose a view mode in which the
+    // file is hidden.
+    $extra_fields = field_extra_fields_get_display('file', $file->type, $view_mode);
+    if (!$extra_fields['file']['visible']) {
       continue;
     }
 
     //@TODO: Display more verbose information about which formatter and what it does.
-    $options[$key] = $title['label'];
-    $file_field = media_get_file_without_label($media, $key, array('wysiwyg' => TRUE));
+    $options[$view_mode] = $view_mode_info['label'];
+    $element = media_get_file_without_label($file, $view_mode, array('wysiwyg' => TRUE));
 
     // Make a pretty name out of this.
-    $formats[$key] = drupal_render($file_field);
+    $formats[$view_mode] = drupal_render($element);
   }
 
   if (!count($formats)) {
@@ -301,14 +293,14 @@ function media_format_form($form, $form_state, $media) {
     '#type' => 'markup',
     '#prefix' => '<h1 class="title">',
     '#suffix' => '</h1>',
-    '#markup' => t('Embedding %filename', array('%filename' => $media->filename)),
+    '#markup' => t('Embedding %filename', array('%filename' => $file->filename)),
   );
 
-  $preview = media_get_thumbnail_preview($media);
+  $preview = media_get_thumbnail_preview($file);
 
   $form['preview'] = array(
     '#type' => 'markup',
-    '#title' => basename($media->uri),
+    '#title' => basename($file->uri),
     '#markup' => drupal_render($preview),
   );
 
@@ -330,7 +322,7 @@ function media_format_form($form, $form_state, $media) {
   // If implemented as an alter, this might not happen, making other alters not
   // be able to work on those fields.
   // @TODO: We need to pass in existing values for those attributes.
-  drupal_alter('media_format_form_prepare', $form, $form_state, $media);
+  drupal_alter('media_format_form_prepare', $form, $form_state, $file);
 
   if (!element_children($form['options'])) {
     $form['options']['#attributes'] = array('style' => 'display:none');
@@ -339,31 +331,38 @@ function media_format_form($form, $form_state, $media) {
   return $form;
 }
 
-
-
 /**
- * Wrapper around field_view_field, returns the file field w/o a label
+ * Returns a drupal_render() array for just the file portion of a file entity.
  *
- * @param Object $media
- * @param string $view_mode
- * @param array $settings
- *  Any attribute overrides to pass to the style formatter.
- * @return array
- *  drupal_renderable array
+ * Optional custom settings can override how the file is displayed.
  */
-function media_get_file_without_label($media, $view_mode, $settings = array()) {
-  $instance = field_info_instance('media', 'file', $media->type);
-  $format = field_get_display($instance, $view_mode, $media);
-  $format['label'] = 'hidden';
-  $format['settings'] = array_merge($format['settings'], $settings);
-  $media->override = $settings;
-  return field_view_field('media', $media, 'file', $format);
+function media_get_file_without_label($file, $view_mode, $settings = array()) {
+  $file->override = $settings;
+
+  // Legacy support for Styles module plugins that expect overridden HTML
+  // attributes in $file->override rather than $file->override['attributes'].
+  if (isset($settings['attributes'])) {
+    $file->override += $settings['attributes'];
+  }
+
+  $element = file_view_file($file, $view_mode);
+
+  // The formatter invoked by file_view_file() can use $file->override to
+  // customize the returned render array to match the requested settings. To
+  // support simple formatters that don't do this, set the element attributes to
+  // what was requested, but not if the formatter applied its own logic for
+  // element attributes.
+  if (!isset($element['#attributes']) && isset($settings['attributes'])) {
+    $element['#attributes'] = $settings['attributes'];
+  }
+
+  return $element;
 }
 
 /**
  * Clears caches that may be affected by the media filter.
  *
- * The media filter calls media_load(). This means that if a media object
+ * The media filter calls file_load(). This means that if a file object
  * is updated, the check_markup() and field caches could return stale content.
  * There are several possible approaches to deal with this:
  *  - Disable filter caching in media_filter_info(), this was found to cause a
diff --git a/includes/media.pages.inc b/includes/media.pages.inc
index c005a2e..51c56b9 100644
--- a/includes/media.pages.inc
+++ b/includes/media.pages.inc
@@ -6,27 +6,22 @@
  */
 
 /**
- * Menu callback; view a single piece of media.
+ * Menu callback; view a single file entity.
  */
-function media_file_view($media) {
+function media_view_page($file) {
   // @todo Implement granular editorial access: http://drupal.org/node/696970.
   //   In the meantime, protect information about private files from being
-  //   discovered by unprivileged users. Media IDs are autoincrement, so one can
+  //   discovered by unprivileged users. File IDs are autoincrement, so one can
   //   attempt discovery by trying to access different media/ID paths. See also
   //   media_browser_list(). This logic potentially belongs within
   //   media_access(), but that would require extending that function's
-  //   signature to accept a $media paramter, and this is temporary code anyway.
-  if (!user_access('administer media') && (file_uri_scheme($media->uri) === 'private')) {
+  //   signature to accept a $file paramter, and this is temporary code anyway.
+  if (!user_access('administer media') && (file_uri_scheme($file->uri) === 'private')) {
     return MENU_ACCESS_DENIED;
   }
 
-  drupal_set_title($media->filename);
-
-  // @TODO maybe something more elegant... ?
-  field_attach_prepare_view('media', array($media->fid => $media), 'media_original');
-  $build['media'][$media->fid] = field_attach_view('media', $media, 'media_original');
-
-  return $build;
+  drupal_set_title($file->filename);
+  return file_view($file, 'media_original');
 }
 
 /**
@@ -36,9 +31,9 @@ function media_preview_ajax() {
   $url = $_GET['url'];
   $uri = media_parse_to_uri($url);
   if ($uri) {
-    $media = file_uri_to_object($uri);
+    $file = file_uri_to_object($uri);
     drupal_json_output(array(
-      'preview' => theme('media_admin_thumbnail', array('file' => $media)),
+      'preview' => theme('media_admin_thumbnail', array('file' => $file)),
     ));
   }
   else {
@@ -52,24 +47,24 @@ function media_preview_ajax() {
 /**
  * Menu callback; presents the Media editing form.
  */
-function media_page_edit($media) {
-  drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => $media->type, '@title' => $media->filename)), PASS_THROUGH);
-  return drupal_get_form('media_edit', $media);
+function media_page_edit($file) {
+  drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => $file->type, '@title' => $file->filename)), PASS_THROUGH);
+  return drupal_get_form('media_edit', $file);
 }
 
 /**
- * Menu callback; presents the Media editing form for multiple items.
+ * Menu callback; presents the Media editing form for multiple file entities.
  */
-function media_page_multiedit($media_items) {
+function media_page_multiedit($files) {
   if (!module_exists('multiform')) {
     drupal_set_message('To edit multiple media items, you must install the multiform module.');
   }
   $i = 0;
-  foreach ($media_items as $item) {
+  foreach ($files as $file) {
     // To maintain unique form_ids, increment this counter.
     // @see media_forms().
     $i++;
-    $forms[] = array("media_edit_$i", $item);
+    $forms[] = array("media_edit_$i", $file);
   }
   
   $form = call_user_func_array('multiform_get_form', $forms);
@@ -87,19 +82,17 @@ function media_page_multiedit($media_items) {
 /**
  * Menu callback; shows delete confirmation form.
  */
-function media_page_delete($media) {
-  drupal_set_title(t('<em>Delete @type</em> @title', array('@type' => $media->type, '@title' => $media->filename)), PASS_THROUGH);
+function media_page_delete($file) {
+  drupal_set_title(t('<em>Delete @type</em> @title', array('@type' => $file->type, '@title' => $file->filename)), PASS_THROUGH);
   // Don't bother showing the form if the item is in use, since we won't allow
-  // them to delete it anyway. Note that file_usage_list() requires a stdClass,
-  // so we can't pass the $media object in directly.
-  $file = file_load($media->fid);
+  // them to delete it anyway.
   $references = file_usage_list($file);
   if (!empty($references)) {
-    return t('The file %title is in use and cannot be deleted.', array('%title' => $media->filename));
+    return t('The file %title is in use and cannot be deleted.', array('%title' => $file->filename));
   }
   else {
-    $files = array($media->fid => $media);
-    return drupal_get_form('media_multiple_delete_confirm', $files, '<front>', 'media/' . $media->fid);
+    $files = array($file->fid => $file);
+    return drupal_get_form('media_multiple_delete_confirm', $files, '<front>', 'media/' . $file->fid);
   }
 }
 
@@ -311,9 +304,9 @@ function media_add_upload_multiple_submit($form, &$form_state) {
 /**
  * Form builder: Builds the edit file form.
  */
-function media_edit($form, $form_state, $media) {
-  field_attach_form('media', $media, $form, $form_state);
-  unset($form['file']);
+function media_edit($form, $form_state, $file) {
+  $form_state['file'] = $file;
+  field_attach_form('file', $file, $form, $form_state);
 
   $form['#attached'] = array(
     'css' => array(drupal_get_path('module', 'media') . '/css/media.css'),
@@ -323,7 +316,7 @@ function media_edit($form, $form_state, $media) {
   $form['#attributes']['class'][] = 'media-image-left';
   $form['#attributes']['class'][] = 'media-edit-form';
   
-  $form['preview'] = field_view_field('media', $media, 'file', 'media_preview');
+  $form['preview'] = file_view_file($file, 'media_preview');
   $form['preview']['#weight'] = -10;
   $form['preview']['#suffix'] = '<div class="no-overflow">';
 
@@ -344,29 +337,34 @@ function media_edit($form, $form_state, $media) {
     '#submit' => array('media_edit_submit'),
   );
 
-  $form['fid'] = array(
-    '#type' => 'hidden',
-    '#value' => $media->fid,
-  );
+  // Add internal file properties needed by media_edit_validate().
+  foreach (array('fid', 'type') as $key) {
+    $form[$key] = array('#type' => 'value', '#value' => $file->$key);
+  }
 
   return $form;
 }
 
+/**
+ * Form validation handler for the media edit form.
+ */
 function media_edit_validate($form, &$form_state) {
-  $media = media_load($form_state['values']['fid']);
-  field_attach_form_validate('media', $media, $form, $form_state);
+  entity_form_field_validate('file', $form, $form_state);
 }
 
 /**
- * Process media_edit form submissions.
+ * Form submit handler for the media submit form.
  */
 function media_edit_submit($form, &$form_state) {
-  $media = media_load($form_state['values']['fid']);
-  field_attach_submit('media', $media, $form, $form_state);
-  media_save($media);
-  $form_state['redirect'] = 'media/' . $media->fid;
+  $file = $form_state['file'];
+  entity_form_submit_build_entity('file', $file, $form, $form_state);
+  file_save($file);
+  $form_state['redirect'] = 'media/' . $file->fid;
 }
 
+/**
+ * Form submit handler for the Delete button on the media edit form.
+ */
 function media_edit_delete_submit($form, &$form_state) {
   $fid = $form_state['values']['fid'];
   $destination = array();
diff --git a/includes/media.theme.inc b/includes/media.theme.inc
index 1b5f373..84855ce 100644
--- a/includes/media.theme.inc
+++ b/includes/media.theme.inc
@@ -283,7 +283,7 @@ function theme_media_thumbnail($variables) {
 
   // Arguments for the thumbnail link
   $thumb = $element['#children'];
-  $target = 'media/' . $element['#object']->fid . '/edit';
+  $target = 'media/' . $element['#file']->fid . '/edit';
   $options = array('query' => $destination, 'html' => true, 'attributes' => array('title' => t('Click to edit details')));
 
   // Element should be a field renderable array... This is a little wonky - admitted.
@@ -310,7 +310,7 @@ function template_preprocess_media_thumbnail(&$variables) {
   // Set the name for the thumbnail to be the filename.  This is done here so
   // that other modules can hijack the name displayed if it should not be the
   // filename.
-  $variables['element']['#name'] = isset($variables['element']['#object']->filename) ? $variables['element']['#object']->filename : NULL;
+  $variables['element']['#name'] = isset($variables['element']['#file']->filename) ? $variables['element']['#file']->filename : NULL;
 }
 
 /**
@@ -320,5 +320,6 @@ function theme_media_formatter_large_icon($variables) {
   $file = $variables['file'];
   $icon_dir = media_variable_get('icon_base_directory') . '/' . media_variable_get('icon_set');
   $icon = file_icon_path((object)$file, $icon_dir);
-  return theme('image', array('path' => $icon));
+  $variables['path'] = $icon;
+  return theme('image', $variables);
 }
diff --git a/includes/media.types.inc b/includes/media.types.inc
index 8f7a813..65a99d6 100644
--- a/includes/media.types.inc
+++ b/includes/media.types.inc
@@ -9,6 +9,83 @@
  */
 
 /**
+ * Implements hook_file_type_info().
+ */
+function media_file_type_info() {
+  $types = array();
+  foreach (media_type_get_types() as $type => $type_object) {
+    $types[$type] = array(
+      'label' => $type_object->label,
+      'weight' => $type_object->weight,
+      'claim callback' => 'media_file_type_media_claim',
+      'default view callback' => 'media_file_type_media_default_view',
+    );
+  }
+  return $types;
+}
+
+/**
+ * Implements hook_file_type_TYPE_claim().
+ *
+ * Returns TRUE if the passed in file should be assigned the passed in type.
+ */
+function media_file_type_media_claim($file, $type) {
+  if (($type_object = media_type_load($type)) && ($function = $type_object->type_callback) && function_exists($function) && $function($file, $type_object->type_callback_args)) {
+    return TRUE;
+  }
+}
+
+/**
+ * Implements hook_file_type_TYPE_default_view().
+ *
+ * Returns a drupal_render() array for displaying the file when there are no
+ * administrator-configured formatters, or none of the configured ones return a
+ * display.
+ */
+function media_file_type_media_default_view($file, $view_mode, $langcode) {
+  // If the file is an image, attempt to display it using the File Entity
+  // module's image formatter.
+  if ($file->type == 'image') {
+    if ($view_mode == 'media_preview') {
+      $image_style = 'square_thumbnail';
+    }
+    elseif ($view_mode == 'media_large') {
+      $image_style = 'large';
+    }
+    elseif ($view_mode == 'media_original') {
+      $image_style = '';
+    }
+    if (isset($image_style)) {
+      $display = array(
+        'type' => 'file_image',
+        'settings' => array(
+          'image_style' => $image_style,
+        ),
+      );
+      $element = file_entity_file_formatter_file_image_view($file, $display, $langcode);
+      if (isset($element)) {
+        return $element;
+      }
+    }
+  }
+
+  // During preview, or when custom attribute values are needed on the displayed
+  // element, use a media icon.
+  if ($view_mode == 'media_preview' || isset($file->override)) {
+    return array(
+      '#theme' => 'media_formatter_large_icon',
+      '#file' => $file,
+    );
+  }
+
+  // Finally, fall back to File module's generic file display.
+  return array(
+    '#theme' => 'file_link',
+    '#file' => $file,
+  );
+}
+
+/**
  *  Update an existing media type or create a new one.
  *
  *  The default media types are currently 'Audio', 'Image', 'Video', and
@@ -40,8 +117,6 @@ function media_type_save(&$type) {
     ->fields((array)$type)
   ->execute();
 
-  media_type_configure_fields($type);
-
   // Clear the caches
   drupal_static_reset('media_type_get_types');
   drupal_static_reset('media_type_get_mime_map');
@@ -49,37 +124,11 @@ function media_type_save(&$type) {
 }
 
 /**
- * Assigns formatters to view_modes for the file field of a media type.
- *
- * Each media type contains a field called "file" and certain stock view modes.
- * This function will assign a format to each view mode. For instance, images
- * would want to use a thumbnail format for files when in the "preview"
- * view_mode.  The called would pass in 'image' for $name and
- * array('media_preview' => 'styles_file_square_thumbnail') to $view_modes_to_formatters.
- *
- * This will also set the "custom_settings" bit to make sure it actually takes effect.
- *
- * @param string $name
- * @param array $view_modes_to_formatters
- * @throws Exception
- * @return void
+ * @todo Remove this function after ensuring that nothing (including update
+ *   functions) call it. It is deprecated with the change from media entity
+ *   containing a file field to just a file entity.
  */
 function media_type_configure_formatters($name, $view_modes_to_formatters) {
-  $instance = field_info_instance('media', 'file', $name);
-  $bundle_settings = field_bundle_settings('media', $name);
-
-  if (!$instance) {
-    throw new Exception('Unable to set formatter preferences for '. $name);
-  }
-  foreach ($view_modes_to_formatters as $view_mode => $formatter) {
-    $bundle_settings['view_modes'][$view_mode]['custom_settings'] = 1;
-    if (!isset($instance['display'][$view_mode])) {
-      $instance['display'][$view_mode] = $instance['display']['default'];
-    }
-    $instance['display'][$view_mode]['type'] = $formatter;
-  }
-  field_bundle_settings('media', $name, $bundle_settings);
-  field_update_instance($instance);
 }
 
 /**
@@ -96,58 +145,6 @@ function media_type_load($name) {
 }
 
 /**
- * Sets up the default fields which a media type needs.
- *
- * Currently, this is just a "file" field which contains the reference to the actual file.
- * @TODO Do we also want to make the file 'description/caption' a field?
- *
- * @param StdClass $media_type
- * @return void
- */
-function media_type_configure_fields($media_type) {
-  $field = field_info_field('file');
-  $instance = field_info_instance('media', 'file', $media_type->name);
-
-  if (empty($field)) {
-    $field = array(
-      'field_name' => 'file',
-      'type' => 'file',
-    );
-    $field = field_create_field($field);
-  }
-
-  // Perhaps the file field exists already, if so skip.
-  if (empty($instance)) {
-    $weight = -5;
-    $instance = array(
-      'field_name' => 'file',
-      'entity_type' => 'media',
-      'bundle' => $media_type->name,
-      'label' => 'File',
-      'widget_type' => 'file_file',
-      'widget' => array(
-        'weight' => $weight,
-      ),
-      'required' => TRUE,
-      'locked' => TRUE,
-      'display' => array(
-        'media_preview' => array(
-          'label' => 'hidden',
-          'type' => 'file_default',
-          'weight' => $weight,
-        ),
-        'media_original' => array(
-          'label' => 'hidden',
-          'type' => 'file_default',
-          'weight' => $weight,
-        ),
-      ),
-    );
-    field_create_instance($instance);
-  }
-}
-
-/**
  *  Loads all media types into an array keyed by machine name and sorted
  *  and weighted lexographically.
  *
@@ -207,22 +204,22 @@ function media_get_type($file) {
 }
 
 /**
- * Default callback used to determine if a piece of media is of a given type.
+ * Default callback used to determine if a file is of a given type.
  *
  * @TODO: document 'any' and 'all' matching.
  *
- * @param $media
- *   The media file asset object.
+ * @param $file
+ *   The file object.
  * @param $args
  *
  * @return unknown_type
  */
-function media_is_type($media, $args) {
+function media_is_type($file, $args) {
   $match_type = !empty($args['match_type']) ? 'any' : $args['match_type'];
   $match_all = $match_type == 'all';
   if (!empty($args['mimetypes'])) {
     foreach ($args['mimetypes'] as $expression) {
-      if (preg_match($expression, $media->filemime)) {
+      if (preg_match($expression, $file->filemime)) {
         if (!$match_all) {
           return TRUE;
         }
@@ -235,7 +232,7 @@ function media_is_type($media, $args) {
   }
 
   if (!empty($args['extensions'])) {
-    if (in_array(pathinfo($media->uri, PATHINFO_EXTENSION), $args['extensions'])) {
+    if (in_array(pathinfo($file->uri, PATHINFO_EXTENSION), $args['extensions'])) {
       if (!$match_all) {
         return TRUE;
       }
@@ -253,15 +250,13 @@ function media_is_type($media, $args) {
 /**
  * Implement hook_media_format_form_prepare_alter
  */
-function media_media_format_form_prepare_alter(&$form, &$form_state, $media) {
-  switch($media->type) {
+function media_media_format_form_prepare_alter(&$form, &$form_state, $file) {
+  switch($file->type) {
     case 'image':
-      // @TODO: Isn't there a $file->description?
-      $description = $media->filename;
-      $form['options']['alt'] = array(
+       $form['options']['alt'] = array(
         '#type' => 'textfield',
         '#title' => t('Description'),
-        '#default_value' => $description,
+        '#default_value' => $file->filename,
         '#description' => t('Alternate text a user will see if the image is not available'),
       );
       break;
@@ -273,7 +268,7 @@ function media_media_format_form_prepare_alter(&$form, &$form_state, $media) {
  */
 function media_type_invalid_files_count() {
   return db_select('file_managed', 'fm')
-    ->condition('type', NULL)
+    ->condition('type', '')
     ->countQuery()
     ->execute()
     ->fetchField();
@@ -298,7 +293,7 @@ function media_type_batch_update($update_existing = FALSE, $no_to_update = NULL,
     ->fields('fm', array('fid'));
 
   if (!$update_existing) {
-    $query->condition('type', NULL);
+    $query->condition('type', '');
   }
 
   if ($no_to_update) {
@@ -314,7 +309,6 @@ function media_type_batch_update($update_existing = FALSE, $no_to_update = NULL,
     if (!$file->fid) {
       throw new Exception('Unable to continue, file was not loaded.');
     }
-    $file->type = media_get_type($file);
     file_save($file);
     $results[] = $fid;
   }
diff --git a/media.info b/media.info
index 782b255..e382383 100644
--- a/media.info
+++ b/media.info
@@ -3,7 +3,7 @@ name = Media
 description = Provides the core Media API
 package = Media
 core = 7.x
-php = 5.2
+
 files[] = media.install
 files[] = media.module
 files[] = includes/media.admin.inc
@@ -14,8 +14,8 @@ files[] = includes/MediaReadOnlyStreamWrapper.inc
 files[] = test/media.types.test
 files[] = test/media.entity.test
 files[] = test/media.filter.test
-dependencies[] = file
+
+dependencies[] = file_entity
 dependencies[] = image
-dependencies[] = file_styles
 
 testing_api = 2.x
diff --git a/media.install b/media.install
index 957add3..c88452e 100644
--- a/media.install
+++ b/media.install
@@ -11,87 +11,35 @@ define('MEDIA_UPDATE_RECORDS_ON_INSTALL', 200);
  * Implements hook_install().
  */
 function media_install() {
-  /**
-   * DISCLAIMER:
-   * Yes I am altering a core table.
-   * No I am not on crack.
-   * Basically, the problem we're facing is the media "type" which is not the
-   * mime type, but is probably computed from it.
-   *
-   * The file table has no type field.  As a result, we would have to either:
-   *
-   * 1). Create a media_files table to join them.  This is nice and clean,
-   * however it requires keeping the tables in sync, and it also means we have
-   * to write our own SQL instead of using BaseEntityController, and that's
-   * kinda scary.
-   *
-   * 2). Make the media type a "computed" field.  Wherein, everytime we loaded
-   * a piece of media, we would need to compute its type from the mime-type.
-   * This is unacceptable from a performance standpoint and also requires us
-   * override the Controller in ways we probably don't want to.
-   *
-   * I know it's a sin, but I think it is also excusable because:
-   *
-   * 1). This is hoping to be a core addition, so think of it as a core patch
-   * that will eventually go in.
-   *
-   * 2). It is adding a new field, so it shouldn't cause any conflicts.  If it
-   * does that INSERT / SELECT code is badly written and should use complete
-   * INSERTS or column names.
-   */
-
-  // Alter {file_managed}.type and add it as an index.
-  db_add_field('file_managed', 'type', _media_type_field_spec());
-  db_add_index('file_managed', 'file_type', array('type'));
+  // @todo Move some of what's in media_enable() into here.
 }
 
 /**
  *  Implement hook_enable().
  */
 function media_enable() {
-  // @TODO: Revisit the default types to provide. For instance, PDFs and text.
   // @TODO: We may need to disable the media bundle & field in hook_disable.
 
-  $view_mode_defaults = array(
-    'media_link'   => 'file_generic',
-    'media_preview' => 'media_large_icon',
-    //@TODO: We need a real "original" formatter
-    'media_original' => 'file_generic',
-    'media_small'   => 'hidden',
-    'media_large'   => 'file_generic',
-  );
-
   $types = array();
 
-  // This is the default type which will be used if no other type is found
+  // Define the default type to be used if no other type is found. Give it a
+  // high weight to ensure it runs last.
   $types['default'] = new StdClass();
   $types['default']->name = 'default';
   $types['default']->label = "Other";
   $types['default']->base = TRUE;
-  //@todo: I don't like this but we have to do something here.
-  $view_mode_settings['default'] = $view_mode_defaults;
-
-  // This should always run last because it is a fallback in case no other
-  // types claim the media.
   $types['default']->weight = 1000;
-  // This is the default.
   $types['default']->type_callback_args = array(
     'match_type' => 'any',
     'mimetypes' => array('/.*/'),
   );
 
-  // Add the common media types.
+  // Define the common media types: image, audio, and video.
   $types['image'] = new StdClass();
   $types['image']->name = 'image';
   $types['image']->label = "Image";
   $types['image']->base = TRUE;
-  $view_mode_settings['image'] = $view_mode_defaults;
-  $view_mode_settings['image']['media_preview'] = 'styles_file_square_thumbnail';
-  $view_mode_settings['image']['media_large'] = 'styles_file_large';
-  $view_mode_settings['image']['media_original'] = 'styles_file_original';
-
-  $types['image']->type_callback_args =
-  array(
+  $types['image']->type_callback_args = array(
     'match_type' => 'all',
     'mimetypes' => array('/^image/'),
     'extensions' => array('jpg', 'jpeg', 'gif', 'png', 'tiff'),
@@ -102,42 +50,36 @@ function media_enable() {
   $types['audio']->name = 'audio';
   $types['audio']->label = "Audio";
   $types['audio']->base = TRUE;
-
-  // @todo: these are image specific, needs to change
-  $view_mode_settings['audio'] = $view_mode_defaults;
   $types['audio']->type_callback_args = array(
     'match_type' => 'all',
     'mimetypes' => array('/^audio/'),
     'extensions' => array('mp3', 'ogg', 'wma'),
     'streams' => array('public', 'private'),
   );
-  
+
   $types['video'] = new StdClass();
   $types['video']->name = 'video';
   $types['video']->label = "Video";
   $types['video']->base = TRUE;
-
-  // @todo: these are image specific, needs to change
-  $view_mode_settings['video'] = $view_mode_defaults;
-  $view_mode_settings['video']['media_preview'] = 'styles_file_square_thumbnail';
-  $view_mode_settings['video']['media_large'] = 'styles_file_large';
-
-  // This is the default, otherwise could provide
-  //$types['video']->type_callback = 'media_default_is_type';
-  $types['video']->type_callback_args =
-  array(
+  $types['video']->type_callback_args = array(
     'match_type' => 'all',
     'mimetypes' => array('/^video/'),
     'extensions' => array('mov', 'mp4', 'avi'),
     'streams' => array('public', 'private'),
   );
 
+  // Create the defined types.
   foreach ($types as $name => $type) {
     media_type_save($type);
-    media_type_configure_formatters($type->name, $view_mode_settings[$type->name]);
+
+    // @todo We hide the file display in the 'small' view mode for legacy
+    //   reasons. Does it still make sense to do so?
+    $bundle_settings = field_bundle_settings('file', $name);
+    $bundle_settings['extra_fields']['display']['file']['media_small'] = array('weight' => 0, 'visible' => FALSE);
+    field_bundle_settings('file', $name, $bundle_settings);
   }
-  
-  // Set permissions
+
+  // Set permissions.
   $roles = user_roles();
   foreach ($roles as $rid => $role) {
     user_role_grant_permissions($rid, array('view media'));
@@ -158,18 +100,6 @@ function media_enable() {
 }
 
 /**
- *  Define the database {file_managed}.type field.
- */
-function _media_type_field_spec() {
-  return array(
-    'description' => 'Type of file for use in Media Bundles',
-    'type' => 'varchar',
-    'length' => 50,
-    'not null' => FALSE,
-  );
-}
-
-/**
  * Implement hook_schema().
  */
 function media_schema() {
@@ -239,21 +169,9 @@ function media_field_schema($field) {
 }
 
 /**
- * Implement hook_schema_alter().
- */
-function media_schema_alter(&$schema) {
-  // Alter {file_managed}.type.
-  $schema['file_managed']['fields']['type'] = _media_type_field_spec();
-}
-
-/**
  * Implement hook_uninstall().
  */
 function media_uninstall() {
-  /**
-   * See comment above in media_install.
-   */
-  db_drop_field('file_managed', 'type');
   foreach (media_variable_default() as $name => $value) {
     media_variable_del($name);
   }
@@ -396,6 +314,8 @@ function media_update_7006() {
  * Changes the preview formatter for non-image types to the icon view.
  */
 function media_update_7007() {
+  // @todo media_type_configure_formatters() is a deprecated function, so remove
+  //   this code entirely?
   drupal_load('module', 'media');
   drupal_load('module', 'field');
 
@@ -420,6 +340,8 @@ function media_update_7008() {
  * Changes the preview formatter for video types to a square thumbnail, like for images.
  */
 function media_update_7009() {
+  // @todo media_type_configure_formatters() is a deprecated function, so remove
+  //   this code entirely?
   drupal_load('module', 'media');
   drupal_load('module', 'field');
   media_type_configure_formatters('video', array('media_preview' => 'styles_file_square_thumbnail'));
@@ -429,6 +351,8 @@ function media_update_7009() {
  * Changes the large formatter for video types to the large file style.
  */
 function media_update_7010() {
+  // @todo media_type_configure_formatters() is a deprecated function, so remove
+  //   this code entirely?
   // This formatter association was added to media_enable() at one point, but
   // without a corresponding update function, so here's that update function.
   drupal_load('module', 'media');
@@ -518,4 +442,121 @@ function media_update_7014() {
  */
 function media_update_7015() {
   registry_rebuild();
-}
\ No newline at end of file
+}
+
+/**
+ * Update Media entities to File entities.
+ */
+function media_update_7016() {
+  // Allow File Entity module to take over the {file_managed}.type field. It
+  // will create new indexes as it needs to, but it doesn't know about old ones,
+  // so delete them.
+  if (db_index_exists('file_managed', 'file_type')) {
+    db_drop_index('file_managed', 'file_type');
+  }
+  module_enable(array('file_entity'));
+
+  // Move all field instances from Media entity to File entity.
+  $instances = field_read_instances(array('entity_type' => 'media'), array('include_inactive' => TRUE, 'include_deleted' => TRUE));
+  foreach ($instances as $instance) {
+    // Skip the old self-referencing file field. It will be deleted later in
+    // this function.
+    if ($instance['field_name'] === 'file') {
+      continue;
+    }
+
+    $fields = field_read_fields(array('id' => $instance['field_id']), array('include_inactive' => TRUE, 'include_deleted' => TRUE));
+    $field = $fields[$instance['field_id']];
+
+    // There is no API for updating the entity_type foreign key within field
+    // data storage. We can do a direct db_update() for when the default SQL
+    // storage back-end is being used, but must skip updating fields that use a
+    // different storage type.
+    // @todo What else should be added here (e.g., logging and/or a message to
+    //   the administrator)?
+    if ($field['storage']['type'] !== 'field_sql_storage' || !module_exists('field_sql_storage') || !$field['storage']['active']) {
+      continue;
+    }
+
+    // Update the data tables.
+    $table_name = _field_sql_storage_tablename($field);
+    $revision_name = _field_sql_storage_revision_tablename($field);
+    db_update($table_name)
+      ->fields(array('entity_type' => 'file'))
+      ->condition('entity_type', 'media')
+      ->condition('bundle', $instance['bundle'])
+      ->execute();
+    db_update($revision_name)
+      ->fields(array('entity_type' => 'file'))
+      ->condition('entity_type', 'media')
+      ->condition('bundle', $instance['bundle'])
+      ->execute();
+
+    // Once all the data has been updated, update the {field_config_instance}
+    // record.
+    db_update('field_config_instance')
+      ->fields(array('entity_type' => 'file'))
+      ->condition('id', $instance['id'])
+      ->execute();
+  }
+
+  // Update the field_bundle_settings configuration variable: move media bundle
+  // settings to file bundles, and move settings of the old self-referencing
+  // file field to the new file pseudo-field.
+  $settings = variable_get('field_bundle_settings', array());
+  if (!isset($settings['file'])) {
+    $settings['file'] = array();
+  }
+  if (isset($settings['media'])) {
+    $settings['file'] = array_merge($settings['file'], $settings['media']);
+    unset($settings['media']);
+  }
+  foreach ($instances as $instance) {
+    if ($instance['field_name'] === 'file' && !$instance['deleted']) {
+      if (isset($instance['widget']['weight'])) {
+        $settings['file'][$instance['bundle']]['extra_fields']['form']['file']['weight'] = $instance['widget']['weight'];
+      }
+      if (isset($instance['display'])) {
+        foreach ($instance['display'] as $view_mode => $display) {
+          if (isset($display['weight'])) {
+            $settings['file'][$instance['bundle']]['extra_fields']['display']['file'][$view_mode]['weight'] = $display['weight'];
+          }
+          if (isset($display['type'])) {
+            $settings['file'][$instance['bundle']]['extra_fields']['display']['file'][$view_mode]['visible'] = ($display['type'] != 'hidden');
+          }
+        }
+      }
+    }
+  }
+  variable_set('field_bundle_settings', $settings);
+
+  // Copy field formatter settings of old self-referencing file field to file
+  // pseudo-field formatter settings.
+  $file_displays = variable_get('file_displays', array());
+  foreach ($instances as $instance) {
+    if ($instance['field_name'] === 'file' && !$instance['deleted']) {
+      if (isset($instance['display'])) {
+        foreach ($instance['display'] as $view_mode => $display) {
+          if (isset($display['type']) && $display['type'] != 'hidden') {
+            $file_formatter = 'file_field_' . $display['type'];
+            $file_displays[$instance['bundle']][$view_mode][$file_formatter]['status'] = TRUE;
+            if (isset($display['settings'])) {
+              $file_displays[$instance['bundle']][$view_mode][$file_formatter]['settings'] = $display['settings'];
+            }
+          }
+        }
+      }
+    }
+  }
+  variable_set('file_displays', $file_displays);
+
+  // Delete the old self-referencing file field instances. If all instances are
+  // deleted, field_delete_instance() will delete the field too.
+  foreach ($instances as $instance) {
+    if ($instance['field_name'] === 'file' && !$instance['deleted']) {
+      field_delete_instance($instance);
+    }
+  }
+
+  field_cache_clear();
+}
diff --git a/media.module b/media.module
index ff1506c..f4715fb 100644
--- a/media.module
+++ b/media.module
@@ -16,13 +16,13 @@
 // A registry of variable_get defaults.
 require_once ( dirname(__FILE__) . '/includes/media.variables.inc');
 
-// Creating media types, entity definition
+// Define media related file types and how to display them.
 require_once ( dirname(__FILE__) . '/includes/media.types.inc');
 
-// Code relating to using media as a field
+// Code relating to using media as a field.
 require_once ( dirname(__FILE__) . '/includes/media.fields.inc');
 
- // Functions for working with [[inline tags]] and wysiwyg editors
+ // Functions for working with [[inline tags]] and wysiwyg editors.
 require_once ( dirname(__FILE__) . '/includes/media.filter.inc');
 
 /* ***************************************** */
@@ -54,6 +54,19 @@ function media_help($path,$arg) {
 }
 
 /**
+ * Implements hook_entity_info_alter().
+ *
+ * Add view modes to the file entity type, appropriate for displaying media.
+ */
+function media_entity_info_alter(&$entity_info) {
+  $entity_info['file']['view modes']['media_link'] = array('label' => t('Link'), 'custom settings' => TRUE);
+  $entity_info['file']['view modes']['media_preview'] = array('label' => t('Preview'), 'custom settings' => TRUE);
+  $entity_info['file']['view modes']['media_small'] = array('label' => t('Small'), 'custom settings' => TRUE);
+  $entity_info['file']['view modes']['media_large'] = array('label' => t('Large'), 'custom settings' => TRUE);
+  $entity_info['file']['view modes']['media_original'] = array('label' => t('Original'), 'custom settings' => TRUE);
+}
+
+/**
  * Implement of hook_menu().
  */
 function media_menu() {
@@ -75,32 +88,24 @@ function media_menu() {
     'access arguments' => array('administer media'),
     'file' => 'includes/media.admin.inc',
   );
-  // For managing different types of media and the fields associated with them.
-  $items['admin/config/media/types'] = array(
-    'title' => 'Media Types',
-    'description' => 'Manage files used on your site.',
-    'page callback' => 'media_admin_type_list',
-    'access arguments' => array('administer media'),
-    'file' => 'includes/media.admin.inc',
-  );
 
   // Settings used for determining the type of media a file is.
-  $items['admin/config/media/types/manage/%media_type'] = array(
-    'title' => 'Manage media',
-    'title callback' => 'media_type_page_title',
-    'title arguments' => array(5),
-    'description' => 'Manage files used on your site.',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('media_admin_type_manage_form', 5),
-    'access arguments' => array('administer media'),
-    'file' => 'includes/media.admin.inc',
-  );
-
-  $items['admin/config/media/types/manage/%media_type/settings'] = array(
-    'title' => 'Settings',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-    'weight' => -1,
-  );
+  // @todo Find a new home for this that integrates with the file_entity module.
+//  $items['admin/config/media/types/manage/%media_type'] = array(
+//    'title' => 'Manage media',
+//    'title callback' => 'media_type_page_title',
+//    'title arguments' => array(5),
+//    'description' => 'Manage files used on your site.',
+//    'page callback' => 'drupal_get_form',
+//    'page arguments' => array('media_admin_type_manage_form', 5),
+//    'access arguments' => array('administer media'),
+//    'file' => 'includes/media.admin.inc',
+//  );
+//  $items['admin/config/media/types/manage/%media_type/settings'] = array(
+//    'title' => 'Settings',
+//    'type' => MENU_DEFAULT_LOCAL_TASK,
+//    'weight' => -1,
+//  );
 
   // Administrative screens for managing media.
   $items['admin/content/media'] = array(
@@ -171,7 +176,7 @@ function media_menu() {
     'file' => 'includes/media.browser.inc',
   );
 
-  $items['media/%media/format-form'] = array(
+  $items['media/%file/format-form'] = array(
     'title' => 'Style selector',
     'description' => 'Choose a format for a piece of media',
     'page callback' => 'drupal_get_form',
@@ -182,21 +187,21 @@ function media_menu() {
     'theme callback' => 'media_dialog_get_theme_name',
   );
 
-  $items['media/%media'] = array(
-    'page callback' => 'media_file_view',
+  $items['media/%file'] = array(
+    'page callback' => 'media_view_page',
     'page arguments' => array(1),
     'access callback' => 'media_access',
     'access arguments' => array('view'),
     'file' => 'includes/media.pages.inc',
   );
 
-  $items['media/%media/view'] = array(
+  $items['media/%file/view'] = array(
     'title' => 'View',
     'type' => MENU_DEFAULT_LOCAL_TASK,
     'weight' => -10,
   );
 
-  $items['media/%media/edit'] = array(
+  $items['media/%file/edit'] = array(
     'title' => 'Edit',
     'page callback' => 'media_page_edit',
     'page arguments'  => array(1),
@@ -222,7 +227,7 @@ function media_menu() {
     'theme callback' => '_node_custom_theme',
   );
 
-  $items['media/%media/delete'] = array(
+  $items['media/%file/delete'] = array(
     'title' => 'Delete',
     'page callback' => 'media_page_delete',
     'page arguments'  => array(1),
@@ -341,7 +346,7 @@ function media_theme() {
     ),
 
     'media_formatter_large_icon' => array(
-      'variables' => array('file' => NULL),
+      'variables' => array('file' => NULL, 'attributes' => array()),
       'file' => 'includes/media.theme.inc',
     ),
   );
@@ -365,29 +370,16 @@ function media_image_default_styles() {
 }
 
 /**
- * Implement hook_file_insert().
- */
-function media_file_insert($file) {
-  $file->type = media_get_type($file);
-  drupal_write_record('file_managed', $file, array('fid'));
-  media_save_attached_file($file);
-}
-
-/**
  * Implement hook_file_update().
  */
 function media_file_update($file) {
-  $file->type = media_get_type($file);
-  media_save_attached_file($file);
+  media_filter_invalidate_caches($file->fid);
 }
 
 /**
  * Implements hook_file_delete().
  */
 function media_file_delete($file) {
-  // @todo: call field_attach_delete() from here, or add media_delete().
-
-  // Remove this file from the {media_filter_usage} table.
   db_delete('media_filter_usage')->condition('fid', $file->fid)->execute();
 }
 
@@ -541,95 +533,18 @@ function media_forms($form_id, $args) {
 /* ***************************************** */
 
 /**
- * Loads a Media Asset
- *
- * @param string $fid
- * @return Media
- */
-function media_load($fid) {
-  if ($files = entity_load('media', array($fid))) {
-    return array_shift($files);
-  }
-  return FALSE;
-}
-
-/**
- * Loads multiple media Assets
+ * Load callback for %media_multi placeholder in menu paths.
  *
  * @param string $fids
- *  Separated by plus(+) signs. e.g. 3+6+12+99
+ *   Separated by space (e.g., "3 6 12 99"). This often appears as "+" within
+ *   URLs (e.g., "3+6+12+99"), but Drupal automatically decodes paths when
+ *   intializing $_GET['q'].
  *
  * @return array
- *  An array of corresponding media entities.
+ *   An array of corresponding file entities.
  */
 function media_multi_load($fids) {
-  $fids = explode(" ", $fids);
-  if ($files = media_load_multiple($fids)) {
-    return $files;
-  }
-}
-
-/**
- * Load media entities from the database.
- *
- * @see entity_load()
- *
- * @param $fids
- *   An array of media file IDs.
- * @param $conditions
- *   An array of conditions on the {file_managed} table in the form
- *   'field' => $value.
- * @param $reset
- *   Whether to reset the internal media_load cache.
- *
- * @return
- *   An array of media objects indexed by fid.
- */
-function media_load_multiple($fids = array(), $conditions = array(), $reset = FALSE) {
-  return entity_load('media', $fids, $conditions, $reset);
-}
-
-/**
- * Save a Media Asset
- *
- * @param $media
- * @return StdClass
- */
-function media_save($media) {
-  field_attach_presave('media', $media);
-  if ($media->fid) {
-    $op = 'update';
-  }
-  else {
-    $op = 'insert';
-  }
-  // Do something here for insert update
-  if ($op == 'insert') {
-    // should never happen I think, except perhaps on import.
-    // @todo: validate bundle, etc
-    field_attach_insert('media', $media);
-  }
-  else {
-    field_attach_update('media', $media);
-    media_filter_invalidate_caches($media->fid);
-  }
-
-  // Since the Media module can't make files into fieldable entities directly
-  // without hacking Drupal core, it does so indirectly via a "media" entity
-  // type. The indirection requires media entities to have one of its fields be
-  // a file field containing the file that defines the media entity itself.
-  // The File module treats this media entity / file field relationship like
-  // any other entity / file field relationship, and increments the file usage
-  // count whenever the media entity is saved, as part of the
-  // field_attach_(insert|update)() execution above. However, we don't want the
-  // media entity's reference to itself to count as usage, since that would
-  // result in all media entities being "in use" and undeletable, so we remove
-  // the self-referential usage record.
-  if ($media->fid) {
-    // file_usage_delete() requires the first argument to be an instance of
-    // stdClass, but $media is an instance of some other class, so we cast it.
-    file_usage_delete((object)(array) $media, 'file', 'media', $media->fid);
-  }
+  return file_load_multiple(explode(" ", $fids));
 }
 
 /**
@@ -949,50 +864,6 @@ function media_parse_to_file($url) {
 }
 
 /**
- *  When we save a file, we need to ensure any required media entity info is
- *  saved as well.
- */
-function media_save_attached_file($file) {
-  if ($file) {
-    // This is terrible.  Just awful.
-    if ($file->status != FILE_STATUS_PERMANENT) {
-      // We only want to manage files which are FILE_STATUS_PERMANENT
-      return;
-    }
-    $defaults = array (
-      'display' => TRUE,
-    );
-
-    // Make a copy of the file object to use as the media object
-    // (file is a field of media and the base table). This is weird.
-    // The problem is, files get managed elsewhere, and when they move from
-    // temporary to PERMANENT files, we want to create media entities for them.
-    // Unfortunately, there is no hook for that, so on every insert / update
-    // we have to look to see if the media entity has been setup
-    // and if not, we have to create it.
-    $media = media_load($file->fid);
-    if (!$media->file) { //Then we haven't setup the media entity.
-      $media->file = array();
-      $media->file[LANGUAGE_NONE] = array();
-      $media->file[LANGUAGE_NONE][0] = (array)$file + $defaults;
-      $media->is_new = TRUE;
-      field_attach_presave('media', $media);
-      field_attach_update('media', $media);
-      //field_attach_insert('media', $media);
-      // @todo Why aren't we calling media_save() instead of the above code.
-      //   Since we aren't, we have to update usage here too, as we do in
-      //   media_save().
-      file_usage_delete((object)(array) $media, 'file', 'media', $media->fid);
-
-      // Also clear cached text formats from here.
-      media_filter_invalidate_caches($media->fid);
-    }
-  }
-  return $media;
-}
-
-
-/**
  * Implements hook_element_info().
  */
 function media_element_info() {
@@ -1024,10 +895,12 @@ function media_element_info() {
   return $types;
 }
 
+/**
+ * #process callback for the media form element.
+ */
 function media_element_process(&$element, &$form_state, $form) {
   $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : 0;
-  //$fid = isset($element['#default_value']['fid']) ? $element['#default_value']['fid'] : 0;
-  $media = media_load($fid);
+  $file = file_load($fid);
   $path = drupal_get_path('module', 'media');
 
   $element['title'] = array(
@@ -1040,8 +913,8 @@ function media_element_process(&$element, &$form_state, $form) {
 
   //@TODO: This should be loaded from the JS in case of a failed form submission.
   $markup = '';
-  if (!empty($media)) {
-    $preview = media_get_thumbnail_preview($media);
+  if (!empty($file)) {
+    $preview = media_get_thumbnail_preview($file);
     $markup = drupal_render($preview);
   }
   $element['preview'] = array(
@@ -1072,11 +945,8 @@ function media_element_process(&$element, &$form_state, $form) {
 //  array_push($parents, 'more_fields');
 //
 //  if ($file) {
-//    $media_wo_file = $file;
-//    $media_wo_file->file = array(); // heh, let's see how this works.
-//    field_attach_prepare_view('media', array($file->fid => $media_wo_file));
-//    entity_prepare_view('media', array($file->fid => $media_wo_file));
-//    $element['more_fields_wrapper']['more_fields'] = field_attach_view('media', $media_wo_file, 'media_preview');
+//    $element['more_fields_wrapper']['more_fields'] = file_view($file, 'media_preview');
+//    unset($element['more_fields_wrapper']['more_fields']['file']);
 //  }
 
   //@HACK: @todo: this is so I can find it in media.js without putting every field in a settings variable.
@@ -1175,13 +1045,13 @@ function _media_save_file_permenently(&$file) {
  * Returns a renderable array with the necessary classes to support a media
  * thumbnail.  Also provides default fallback images if no image is available.
  *
- * @param $media
+ * @param $file
  *
  * @return array
  *   Renderable array.
  */
-function media_get_thumbnail_preview($media, $link = NULL) {
-  $preview = field_view_field('media', $media, 'file', 'media_preview');
+function media_get_thumbnail_preview($file, $link = NULL) {
+  $preview = file_view_file($file, 'media_preview');
   $preview['#show_names'] = TRUE;
   $preview['#add_link'] = $link;
   $preview['#theme_wrappers'][] = 'media_thumbnail';
@@ -1189,46 +1059,6 @@ function media_get_thumbnail_preview($media, $link = NULL) {
 }
 
 /**
- * Implements hook_entity_info().
- */
-function media_entity_info() {
-  $view_modes = media_field_view_modes('media');
-  $return = array(
-    'media' => array(
-      'label' => t('Media'),
-      'base table' => 'file_managed',
-      'fieldable' => TRUE,
-      'view modes' => $view_modes,
-      'entity keys' => array(
-        'id' => 'fid',
-        'bundle' => 'type', // @TODO: Why 'type' here...
-      ),
-      'bundle keys' => array(
-        'bundle' => 'name', // @TODO: But 'name' here?
-      ),
-      'bundles' => array(),
-    ),
-  );
-
-  $media_types = media_type_get_types();
-
-  foreach ($media_types as $type => $bundle_info) {
-    $return['media']['bundles'][$type] = (array)$bundle_info;
-    $return['media']['bundles'][$type]['admin'] = array(
-      'label' => $bundle_info->label,
-      'path' => 'admin/config/media/types/manage/%media_type',
-      'real path' => 'admin/config/media/types/manage/' . $type,
-      'bundle argument' => 5,
-      // @TODO: Add an 'administer media types' perm?
-      'access arguments' => array('administer site configuration'),
-    );
-  }
-
-  // @TODO: Do we need to define search-related view modes for attached fields?
-  return $return;
-}
-
-/**
  * Check that the media is one of the selected types.
  *
  * @param $file
diff --git a/modules/media_youtube_test/media_youtube_test.info b/modules/media_youtube_test/media_youtube_test.info
new file mode 100644
index 0000000..c109386
--- /dev/null
+++ b/modules/media_youtube_test/media_youtube_test.info
@@ -0,0 +1,8 @@
+
+name = "Media YouTube Test"
+description = "Tests integration with the http://drupal.org/project/media_youtube module."
+package = Testing
+core = 7.x
+php = 5.2
+dependencies[] = media_youtube
+;hidden=TRUE
diff --git a/modules/media_youtube_test/media_youtube_test.install b/modules/media_youtube_test/media_youtube_test.install
new file mode 100644
index 0000000..4dece41
--- /dev/null
+++ b/modules/media_youtube_test/media_youtube_test.install
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * Implements hook_enable().
+ */
+function media_youtube_test_enable() {
+  // Setup default configuration for displaying YouTube media. Use relatively
+  // high weights so that existing video display settings configured by the
+  // administrator take precedence.
+  $file_displays = variable_get('file_displays', array());
+
+  // Within these view modes, attempt to display a video, but where video
+  // doesn't work (e.g., inside a WYSIWYG editor), fall back to a YouTube
+  // generated image.
+  foreach (array('default', 'media_large', 'media_original') as $view_mode) {
+    $file_displays['video'][$view_mode]['media_youtube_test_video'] = array(
+      'status' => TRUE,
+      'weight' => 10,
+    );
+    $file_displays['video'][$view_mode]['media_youtube_test_image'] = array(
+      'status' => TRUE,
+      'weight' => 11,
+      'settings' => array('image_style' => ''),
+    );
+  }
+
+  // Within a Media preview, display a square thumbnail image.
+  $file_displays['video']['media_preview']['media_youtube_test_image'] = array(
+    'status' => TRUE,
+    'weight' => 10,
+    'settings' => array('image_style' => 'square_thumbnail'),
+  );
+
+  variable_set('file_displays', $file_displays);
+}
diff --git a/modules/media_youtube_test/media_youtube_test.module b/modules/media_youtube_test/media_youtube_test.module
new file mode 100644
index 0000000..ed9b651
--- /dev/null
+++ b/modules/media_youtube_test/media_youtube_test.module
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * Implements hook_file_formatter_info().
+ */
+function media_youtube_test_file_formatter_info() {
+  $formatters['media_youtube_test_video'] = array(
+    'label' => t('YouTube Video'),
+    'default settings' => array('width' => '560', 'height' => '340'),
+    'view callback' => 'media_youtube_test_file_formatter_media_youtube_test_video_view',
+    'settings callback' => 'media_youtube_test_file_formatter_media_youtube_test_video_settings',
+  );
+  $formatters['media_youtube_test_image'] = array(
+    'label' => t('YouTube Image'),
+    'default settings' => array('image_style' => ''),
+    'view callback' => 'media_youtube_test_file_formatter_media_youtube_test_image_view',
+    'settings callback' => 'media_youtube_test_file_formatter_media_youtube_test_image_settings',
+  );
+  return $formatters;
+}
+
+
+/**
+ * Implements hook_file_formatter_FORMATTER_view().
+ */
+function media_youtube_test_file_formatter_media_youtube_test_video_view($file, $display, $langcode) {
+  $scheme = file_uri_scheme($file->uri);
+  // WYSIWYG does not yet support video inside a running editor instance.
+  if ($scheme == 'youtube' && empty($file->override['wysiwyg'])) {
+    $element = array(
+      '#theme' => 'media_youtube_video',
+      '#uri' => $file->uri,
+      '#width' => isset($file->override['width']) ? $file->override['width'] : $display['settings']['width'],
+      '#height' => isset($file->override['height']) ? $file->override['height'] : $display['settings']['height'],
+    );
+    return $element;
+  }
+}
+
+/**
+ * Implements hook_file_formatter_FORMATTER_settings().
+ */
+function media_youtube_test_file_formatter_media_youtube_test_video_settings($form, &$form_state, $settings) {
+  $element = array();
+  $element['width'] = array(
+    '#title' => t('Width'),
+    '#type' => 'textfield',
+    '#default_value' => $settings['width'],
+  );
+  $element['height'] = array(
+    '#title' => t('Height'),
+    '#type' => 'textfield',
+    '#default_value' => $settings['height'],
+  );
+  return $element;
+}
+
+/**
+ * Implements hook_file_formatter_FORMATTER_view().
+ */
+function media_youtube_test_file_formatter_media_youtube_test_image_view($file, $display, $langcode) {
+  $scheme = file_uri_scheme($file->uri);
+  if ($scheme == 'youtube') {
+    $wrapper = file_stream_wrapper_get_instance_by_uri($file->uri);
+    if (!empty($display['settings']['image_style'])) {
+      $element = array(
+        '#theme' => 'image_style',
+        '#style_name' => $display['settings']['image_style'],
+        '#path' => $wrapper->getLocalThumbnailPath(),
+      );
+    }
+    else {
+      $element = array(
+        '#theme' => 'image',
+        '#path' => $wrapper->getOriginalThumbnailPath(),
+      );
+    }
+    return $element;
+  }
+}
+
+/**
+ * Implements hook_file_formatter_FORMATTER_settings().
+ */
+function media_youtube_test_file_formatter_media_youtube_test_image_settings($form, &$form_state, $settings) {
+  $element = array();
+  $element['image_style'] = array(
+    '#title' => t('Image style'),
+    '#type' => 'select',
+    '#options' => image_style_options(FALSE),
+    '#default_value' => $settings['image_style'],
+    '#empty_option' => t('None (original image)'),
+  );
+  return $element;
+}
