Media Mover is a set of modules which allows website administrators to easily create complex file conversion processes. The core of Media Mover is the API which creates a set of rules allowing multiple modules to interact with a file.

If you are familar with Drupal's hook_nodeapi, hook_media_mover() should
feel somewhat familiar. Like hook_nodeapi(), hook_media_mover() provides
access to several differnt parts of a Media Mover configuration life cycle.
Media Mover API governs the processes that call the hook, requesting
various data from modules like configuration forms, attempting to simplify
the integration of modules who have no awareness of one another. Like
hook_nodeapi(), hook_media_mover() only sets the rules of the game but
never cares about who the players might be or what they are doing with the
data.

Media Mover offers developers a unified interface for configuration of multiple modules making it easier for the admin user to handle complex configuration. Further, Media Mover offers an simple way for modules to pass data across an execution chain, allowing for complex media handling with no demand for the
developer to rewrite base level function that Media Mover provides.

While Media Mover was initially intended as a rich media solution, Media Mover merely describes a process of getting something, doing something with it, and putting it some where. This could be text items, data objects, files, rich media, or any other number of abstract concepts. Media Mover does tend to be file centric, however its use cases are not limited to files.

First some definitions:

  • configuration: In the context of Media Mover, a configuration is a group of actions that are chained together. A configuration is comprised of of harvest, process, storage, complete. A configuration can have some additional attributes which constitute "advanced" options
  • action: a single part of the configuration chain. A harvest, process, storage, or complete process
  • action id: specifies a process inside of a module which implements the hook_media_mover()
  • harvest: describes the configuration action of finding items for Media Mover and making them available to Media Mover
  • process: describes the configuration action of manipulating files
  • storage: describes the configuration action of taking an individual item and placing it in storage
  • complete: describes a final configuration action that can be added on the Media Mover process chain.

The following is an implementation of the Media Mover hook in the mm_ffmpeg
module. This gives a general example of how the hook is implemented. There
are other options which are shown below.

<?php 
/**
 * Implementation of media_mover hook
 * @param string $op is the operator
 * @param int $action_id is the action to call
 * @param array $configuration is the media mover configuration currently being run
 * @param array $file is the media mover file array
 * @param object $full_config is a full media mover configuration
 * 
 */
function mm_ffmpeg_media_mover($op, $action_id = null, $configuration = array(), &$file = array(), $full_config, $nid ) {
  switch ($op) {
    // identify this module
    case 'name':
      return "FFmpeg module";
    break;

    // define this module's actions
    // @return array
    case 'actions':
      return array( 
        'process' => array( 
          1 => 'convert video',
          2 => 'create thumbnail from video',
         )        
      );
    break;

    // edit a configuration option set
    // $configuration is a config array
    case 'config':
      switch ($action_id) {
        case "1": //convert video
          return mm_ffmpeg_config($configuration);
        break;
        case "2": //create thumbnail from video
          return mm_ffmpeg_config_thumb($configuration);
        break;
      }
    break; 

    // validate configuration
    // @return array of error messages and elements that are errors
    case 'config_validate':
      // NOTE: this has been depreciated. Use element validate in your configuration form. See below.
  $form['mm_s3'] = array(
  '#element_validate' => array('mm_s3_validate_configuration', array('mm_s3'))
    	}
    break;

    // define global settings for this module
    // @return array drupal form array
    case 'admin':
      return mm_ffmpeg_admin();
    break;

    // define any directories that might need to be created
    // @return array
    case 'directories':
      return array('ffmpeg/converted', 'ffmpeg/thumbnails');
    break;

    // define the processing that this module does
    // @return file path or false
    case 'process':
      switch ($action_id) {
        case '1': //convert video
          return mm_ffmpeg_video($file, $configuration);
        break;
        case '2': //create thumbnail from video
           return mm_ffmpeg_video_thumbnail($file, $configuration);
        break;
      }
    break;
  }
  return;
}
?>

Media Mover uses an $op variable to control what aspects of the hook called.
This is further modified by the $action_id variable which tells the hook which
action the $op is related to. A Media Mover hook can perform multiple
operations which the $action_id differentiates. Not all $op functions require
an $action_id but Media Mover API will pass when it is necessary

name

<?php
    // identify this module
    // @return string
    case 'name':
      return "FFmpeg module";
    break;
?>

The $op = 'name' merely defines the name of this module. This is used for
display purposes and helps the admin identify which module they are choosing
configurations for. Return a string of text.

actions

<?php
    // define this module's actions   
    // @return array
    case 'actions':
      return array( 
        'process' => array( 
          1 => 'convert video',
          2 => 'create thumbnail from video',
         )        
      );
    break;
?>

$op = 'actions' is the most important part of the Media Mover API
implementation. This defines both what the module can do during a Media Mover
configuration but also how many actions it has. The returned array is keyed
based on the roles that the module can play. Possible values are harvest,
process, storage, complete*. For each role, a module can implement specific
actions, for example, in mm_ffmpeg, we see that there are two defined actions.
These have internally (to the specific module) unique numeric ids, and a text
string which makes it easier for a human to identify them

config

<?php
    // edit a configuration option set
    // $configuration is a config array
    // @return array drupal form array
    case 'config':
      switch ($action_id) {
        case "1": //convert video
          return mm_ffmpeg_config($configuration);
        break;
        case "2": //create thumbnail from video
          return mm_ffmpeg_config_thumb($configuration);
        break;
      }
    break; 
?>

In $op = 'config' some of the interesting aspects of Media Mover appear.
The process of calling the Drupal form for adding a new configuration to
Media Mover is a single function call to a drupal_get_form(). Media Mover
then fires the hook_media_mover() with $op = 'config'. Each module returns
its form data. The problem here is that the name space is relatively small
and collisions of array keys are inevitable. So Media Mover API prefixes all
of the incoming form elements with the specific role, module, and action id
which makes it possible to build really complicated forms without much trouble.
Thankfully, the module developer does not have to worry about this at all. All
that is required to return a specific configuration form is to return the
standard Drupal form array- return $form.

$configuration is an array of data that is being passed back into the form,
which would be the same as $form_values for this form. Note that other form
data is not accessible here- only the data that the $form returned by the
$op = 'config' is.

Each hook_media_mover() can implement more than one process, and each of these
should pass back its own configuration so that Media Mover can keep track
of each set of data. In the example above, you can see that mm_ffmpeg has
two different configurations that are passed back to Media Mover.

Configuration Validation

Previously, Media Mover used a specific hook for validation. This is being depreciated. Modules should the form array in the configuration form to specify a validation function:

<?php
  $form['mm_s3'] = array(
    '#type' => 'fieldset',
    '#title' => t('S3 configuration'),
    '#collapsed' => false,
    '#element_validate' => array('mm_s3_validate_configuration', array('mm_s3'))
  );
?>

The only thing different about the validation process is how you retrieve specific form data. Because Media Mover does prefixing of the form elements, you need a function that will pull your values out of the form data:

<?php
  function mm_s3_validate_configuration($element, &$form_state) {
  // get the data for this module, if it was called
  if ($values = media_mover_api_extract_form_data($form_state['values'], 'mm_s3')) {
    
    // buckets must be lower case
    if ($values['mm_s3_bucket'] != strtolower($values['mm_s3_bucket'])) {
      form_error($element, t('Your bucket name must be lower case.'));
    }
?>

Once you get $values, you can use the data as you would expect. Likely this will change in later versions of Media Mover so that you do not need the media_mover_api_extract_form_data() function. When this is the case, this function will merely return $form_sate['values'] to keep things compatible.

admin

<?php
    // define global settings for this module
    // @return array drupal form array
    case 'admin':
      return mm_ffmpeg_admin();
    break;
?>

The $op = 'admin' functions similarly to the 'config' option, however in this
case, modules are responsible for naming their own forms correctly. This
servers as the global settings for this particular module and uses Drupal's
standard admin settings. Media Mover just groups these for ease of use- this
is not a required. Return $form array and make sure that your form elements
are named well as to not conflict with other system variables.

harvest, process, storage, complete

<?php
    // define the processing that this module does
    // @return string or false
    case 'process':
      switch ($action_id) {
        case '1': //convert video
          return mm_ffmpeg_video($file, $configuration);
        break;
        case '2': //create thumbnail from video
           return mm_ffmpeg_video_thumbnail($file, $configuration);
        break;
      }
    break;
?>

When Media Mover begins running a configuration, it starts by going through
each of the kinds of actions that are performed in the module. Media Mover
first harvests any new files for this configuration. Once this is done,
Media Media mover then looks for an files that are ready for processing, then
storage, then complete.

At each stage in the run of the configuration, hook_media_mover()
$op = $action is called, where $action could be one of harvest, process,
storage, complete. These are not necessarily called sequentially. Because Media Mover can be setup to do multiple simultaneous processing, another thread could start a process before the current one is completed. Files are protected from overlapping runs, but you should not assume that a single file goes through each step. Files are operated on as a batch from one step to the next.

$action_id is available here if a module defines multiple processes, Media Mover will call the correct $action_id with the right $configuration data. $configuration only contains the specific data for this process. If the full configuration object is needed, you use $full_config here.

Media Mover expects the return of a file path or true if the process was successful or false if unsuccessful. $file is passed by reference so it is possible add additional data to the $file array which will be saved by Media Mover. If you pass true back, Media Mover api will use the file from the last operation to find get your file.

For modules that harvest, $nid can be passed to re-run a harvest operation on a specific $nid. You need to write your harvest operations so that they can take the optional $nid (if it is applicable).

There is a second difference for harvest actions. Where other actions are dealing with single files, harvests are responsible collecting a group of items at a time. Since some harvest operations may not be able to limit how many items they collect at a time (time sensitive things like email, ftp, etc), the API itself does not limit this, thus harvest actions must return a full array of files. Of course, individual modules may choose to limit how many files are harvested at a given time.

The most critical item that a harvest action needs to hand back is setting the harvest_file value for the specific file. Most modules provide additional data, but that is not required.

Harvest actions are responsible for moving files if the harvested items are not on the local filesystem, and they are also responsible for doing uniqueness checking as the characteristics of what makes the specific item unique are not always clear.

config_add

<?php
    // perform an action after a configuration is added
    case 'config_add':
       yourfunction($configuration);
    break;
?>

Since the configuration add process can only add the data that is in the form at the time, the config_add op offers a way for modules to add data or manipulate the configuration. A use case is the mm_node module which allows a Media Mover configuration to harvest files from another configuration. The parent child relationship needs to be set in this instance, however, this is a highly specific operation and should not be in the core api, so instead we use the hook, and allow mm_node to update the configuration once it has been created.

directories

<?php
    // define any directories that might need to be created
    // @return array
    case 'directories':
      return array('ffmpeg/converted', 'ffmpeg/thumbnails');
    break;    
?>

The $op = 'directories' is some what of a legacy option. It allows modules
to require directories under the media_mover directory. Currently this is
relative to the Drupal files directory, however, this option is setup
to provide alternate file repositories. This is not required. Return array of
directories.

delete_file

<?php
    // delete a file
    case 'delete_file':
      mm_s3_delete_file($file, $configuration);
    break;
?>

There are a few instances when a file can be deleted- in the Media Mover
interface, an admin can delete an individual file, a configuration, or a node
that has Media Mover files associated with it. A module has to be responsible
for removing only its files. This is called for each file being deleted.
The full $file array that is being deleted is passed, as well as the
specific $configuration that is related to this $file.

fetch

<?php
    // add data to a file
    // @ return array
    case 'fetch':
       return yourfunction($configuration, $file);
    break;
?>

Some modules will store their own data and want to add that onto the Media
Mover item when it is being loaded. The fetch hook allows module developers
to do this. Typically, this data will be available under
$node->media_mover['additional_items'][$module][$action_id]
Please note that Media Mover does cache this per node as this is potentially
an expensive process to run. If your data is dynamic, consider writing
your own hook_nodeapi() so Media Mover won't try to cache it.

update

<?php
    // add data to a file
    case 'update':
       yourfunction($configuration, $file);
    break;
?>

Update functions similarly to fetch in that it would be used by modules which
either need to store records of the Media Mover items in some other kind of
database, or need to trigger some kind of function when the the item is being
updated. Media Mover has already saved the file so you can not modify what
what has already been saved.

config_add

<?php 
    // save configuration data when a new config is created
    case 'config_add':
      if ($action == 1) {
        mm_node_config_add($configuration);
      }
    break;
?>

Config add is a specialized hook used for modules to add additional data to a configuration when it is created. MM Node uses this to add hierarchy data to a configuration, for example.

* at somepoint, Media Mover is going to break away from the hard set roles
and allow chaining of specific actions.