Index: includes/ooyala.api.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ooyala/includes/ooyala.api.inc,v
retrieving revision 1.4
diff -u -r1.4 ooyala.api.inc
--- includes/ooyala.api.inc	13 Dec 2010 03:57:25 -0000	1.4
+++ includes/ooyala.api.inc	14 Dec 2010 18:31:36 -0000
@@ -75,7 +75,7 @@
   // Verify that we're even setup correctly.
   if ($response->data == 'unknown pcode') {
     drupal_set_message(t('Ooyala configuration invalid. Please check your !settings.', array('!settings' => l('settings', 'admin/settings/ooyala'))), 'error');
-    return;
+    return FALSE;
   }
 
   // Populate all the video entries with the results.
@@ -91,7 +91,9 @@
       // Convert all items to strings. This ensures that empty items do not
       // return as child SimpleXML objects.
       foreach ($video as $key => $value) {
-        $video[$key] = (string) $value;
+        if (!in_array($key, array('labels'))) {
+          $video[$key] = (string) $value;
+        }
       }
 
       // Convert labels.
@@ -101,7 +103,8 @@
         foreach ($labels as $label) {
           // TODO: It would be nice if labels had a label ID here to use as the
           // key. API change required from Ooyala.
-          $video['labels'][] = $label[0];
+          $olid = ooyala_api_label_id($label[0]);
+          $video['labels'][$olid] = (string) $label[0];
         }
       }
 
@@ -143,7 +146,7 @@
   }
 
   // Build a list of videos to query that we do not yet have retrieved.
-  $query_embedcodes = $embedcodes;
+  $query_embedcodes = array();
   foreach ($embedcodes as $key => $embedcode) {
     if (!isset($videos[$embedcode])) {
       $query_embedcodes[] = $embedcode;
@@ -152,24 +155,20 @@
 
   // Query for any new videos that need to be retrieved.
   if (!empty($query_embedcodes)) {
-    $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
-    $request = $ooyala_api->signed_params(
-      array(
-        'embedCode' => implode(',', $query_embedcodes),
-      )
-    );
-    $response = drupal_http_request("http://api.ooyala.com/partner/query?$request");
-
     // Default each embedcode to FALSE in case it is not retrieved.
     foreach ($query_embedcodes as $embedcode) {
       $videos[$embedcode] = FALSE;
     }
 
     // Populate all the video entries with the results.
-    if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
-      foreach ($xmldoc->children() as $result) {
-        $video = (array) $result;
-        $videos[$video['embedCode']] = $video;
+    $params = array(
+      'embedCode' => implode(',', $query_embedcodes),
+      'fields' => 'labels',
+    );
+    $results = ooyala_api_video_query($params);
+    if ($results) {
+      foreach ($results as $embedcode => $video) {
+        $videos[$embedcode] = $video;
       }
     }
   }
@@ -198,6 +197,215 @@
 }
 
 /**
+ * Remove labels from a video.
+ */
+function ooyala_api_video_label_remove($embedcode, $labels, $recursive = TRUE) {
+  $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+  $request = $ooyala_api->signed_params(
+    array(
+      'embedCodes' => is_string($embedcode) ? $embedcode : implode(',', $embedcode),
+      'labels' => is_string($labels) ? $labels : implode(',', $labels),
+      'mode' => 'unassignLabels',
+      'includeSublabels' => $recursive ? 'true' : 'false',
+    )
+  );
+  $response = drupal_http_request("http://api.ooyala.com/partner/labels?$request");
+
+  $success = FALSE;
+  if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
+    $success = ((string) $xmldoc[0]) == 'ok';
+  }
+
+  return $success;
+}
+
+/**
+ * Set labels on a video.
+ *
+ * @param $embedcode
+ *   An Ooyala embed code.
+ * @param $labels
+ *   An array of labels to set on a video.
+ */
+function ooyala_api_video_label_add($embedcode, $labels) {
+  $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+  $request = $ooyala_api->signed_params(
+    array(
+      'embedCodes' => is_string($embedcode) ? $embedcode : implode(',', $embedcode),
+      'labels' => is_string($labels) ? $labels : implode(',', $labels),
+      'mode' => 'assignLabels',
+    )
+  );
+  $response = drupal_http_request("http://api.ooyala.com/partner/labels?$request");
+
+  $success = FALSE;
+  if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
+    $success = ((string) $xmldoc[0]) == 'ok';
+  }
+
+  return $success;
+}
+
+/**
+ * Retrieve a label ID from Ooyala based on label name.
+ */
+function ooyala_api_label_id($label_name) {
+  // TODO: Swap out for a more efficient API call when Ooyala adds ability to
+  // retrieve label name based on ID.
+  $label_name = rtrim($label_name, '/');
+  $parent_name = substr($label_name, 0, strrpos($label_name, '/'));
+  if ($parent_name == '') {
+    $parent_name = '<root>';
+  }
+  $labels = ooyala_api_label_list($parent_name);
+  return array_search($label_name, $labels);
+}
+
+/**
+ * Retrieve a label name from Ooyala based on label ID.
+ */
+function ooyala_api_label_name($label_id) {
+  $labels = ooyala_api_label_list();
+  return isset($labels[$label_id]) ? $labels[$label_id] : FALSE;
+}
+
+/**
+ * Retrieve a list of labels from Ooyala.
+ *
+ * @param $parent
+ *   The parent label under which to retrieve all labels. Use "/" to
+ *   retrieve all labels. Use '<root>' to return only the root labels.
+ *
+ *   Note that Ooyala will accept both a label path (such as /Tags) or an
+ *   Ooyala label ID, which is a 32 character hash.
+ * @param $reset
+ *   Reset the internal cache of labels to retrieve.
+ */
+function ooyala_api_label_list($parent = '/', $reset = FALSE) {
+  static $labels;
+
+  if ($reset || !isset($labels)) {
+    $labels = array();
+  }
+
+  if (isset($parent) && !isset($labels[$parent])) {
+    $labels[$parent] = array();
+
+    // Query for any new videos that need to be retrieved.
+    $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+    $params = array(
+      'mode' => 'listSubLabels',
+      'label' => $parent,
+    );
+    // Currently it is impossible to get just a list of top-level terms, so we
+    // have to get the full list.
+    if ($parent == '<root>') {
+      $params['label'] = '/';
+    }
+
+    $request = $ooyala_api->signed_params($params);
+    $response = drupal_http_request("http://api.ooyala.com/partner/labels?$request");
+
+    // Populate all the video entries with the results.
+    if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
+      // Verify that we're even setup correctly.
+      if (isset($xmldoc['message']) && 'Invalid pcode.' == (string) $xmldoc['message']) {
+        return FALSE;
+      }
+
+      foreach ($xmldoc->children() as $result) {
+        $row = (array) $result;
+        $label = rtrim($row[0], '/');
+
+        // If building a list of root labels, skip all non-root labels.
+        $last_slash = strrpos($label, '/');
+        if ($parent == '<root>' && $last_slash !== 0) {
+          continue;
+        }
+        $labels[$parent][$row['@attributes']['id']] = $label;
+      }
+    }
+
+    // Sort all the labels so that all root items are first, then all second
+    // level, etc. This sets us up for width-first operations.
+    uasort($labels[$parent], '_ooyala_label_sort');
+  }
+
+  return isset($parent) ? $labels[$parent] : TRUE;
+}
+
+/**
+ * Add a label to the Ooyala label hierarchy.
+ *
+ * @param $label_name
+ *   The name of the label, including the label path.
+ *
+ * @return
+ *   The Ooyala Label ID of the new label.
+ */
+function ooyala_api_label_add($label_name) {
+  $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+  $request = $ooyala_api->signed_params(array(
+    'mode' => 'createLabels',
+    'labels' => is_string($label_name) ? $label_name : implode(',', $label_name),
+  ));
+  $response = drupal_http_request("http://api.ooyala.com/partner/labels?$request");
+
+  // Retrieve the label ID for the newly created label.
+  $success = FALSE;
+  $label_id = FALSE;
+  if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
+    $success = ((string) $xmldoc[0]) == 'ok';
+    if ($success) {
+      $label_id = ooyala_api_label_id($label_name);
+    }
+  }
+
+  return $label_id;
+}
+
+/**
+ * Rename a label in Ooyala.
+ *
+ * This function currently only accepts label paths, not label IDs.
+ */
+function ooyala_api_label_rename($old_label_name, $new_label_name) {
+  $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+  $request = $ooyala_api->signed_params(array(
+    'mode' => 'renameLabel',
+    'oldlabel' => $old_label_name,
+    'newlabel' => $new_label_name,
+  ));
+  $response = drupal_http_request("http://api.ooyala.com/partner/labels?$request");
+
+  $success = FALSE;
+  if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
+    $success = ((string) $xmldoc[0]) == 'ok';
+  }
+
+  return $success;
+}
+
+/**
+ * Delete a label in Ooyala.
+ */
+function ooyala_api_label_delete($label_name) {
+  $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+  $request = $ooyala_api->signed_params(array(
+    'mode' => 'deleteLabels',
+    'labels' => $label_name,
+  ));
+  $response = drupal_http_request("http://api.ooyala.com/partner/labels?$request");
+
+  $success = FALSE;
+  if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
+    $success = ((string) $xmldoc[0]) == 'ok';
+  }
+
+  return $success;
+}
+
+/**
  * Fetch and save a video thumbnail from the Ooyala server.
  *
  * @param $embedcode
@@ -252,3 +460,14 @@
 
   return $thumb_path;
 }
+
+/**
+ * Custom sort function to sort labels, putting root items first.
+ */
+function _ooyala_label_sort($a, $b) {
+  $cmp = substr_count($a, '/') - substr_count($b, '/');
+  if ($cmp == 0) {
+    $cmp = strcasecmp($a, $b);
+  }
+  return $cmp;
+}
Index: includes/ooyala.batch.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ooyala/includes/ooyala.batch.inc,v
retrieving revision 1.2
diff -u -r1.2 ooyala.batch.inc
--- includes/ooyala.batch.inc	13 Dec 2010 03:57:25 -0000	1.2
+++ includes/ooyala.batch.inc	14 Dec 2010 18:31:36 -0000
@@ -24,6 +24,9 @@
     $video = array_shift($context['sandbox']['videos']);
     $node = ooyala_create_node($video, $type);
 
+    // Pull in the thumbnail for the new node.
+    ooyala_api_fetch_image($video['embedCode']);
+
     // Store result for post-processing in the finished callback.
     $context['results'][] = l($node->title, 'node/' . $node->nid);
 
Index: includes/ooyala.pages.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ooyala/includes/ooyala.pages.inc,v
retrieving revision 1.19
diff -u -r1.19 ooyala.pages.inc
--- includes/ooyala.pages.inc	13 Dec 2010 03:57:25 -0000	1.19
+++ includes/ooyala.pages.inc	14 Dec 2010 18:31:37 -0000
@@ -10,6 +10,7 @@
  * Menu callback; Display global configuration options for Ooyala fields.
  */
 function ooyala_settings_form(&$form_state) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.api');
   $form['ooyala_global_pcode'] = array(
     '#type' => 'textfield',
     '#default_value' => variable_get('ooyala_global_pcode', ''),
@@ -73,8 +74,9 @@
   );
 
   if (empty($options)) {
-    $form['content_type']['#disabled'] = TRUE;
-    $form['content_type']['#options'][''] = t('No available types');
+    $form['ooyala_primary_content_type']['#disabled'] = TRUE;
+    $form['ooyala_primary_content_type']['#options'][''] = t('No available types');
+    $form['ooyala_primary_content_type']['#description'] = t('There are currently no content types that contain an Ooyala field. Add an Ooyala field under <em>Manage fields</em> from the <a href="!url">Content type configuration</a>.', array('!url' => url('admin/content/types')));
   }
 
   if (module_exists('thickbox')) {
@@ -122,6 +124,60 @@
     '#default_value' => variable_get('ooyala_autopublish', FALSE)
   );
 
+  $form['ooyala_taxonomy'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Taxonomy vocabulary syncing'),
+    '#description' => t('These options allow you to syncronize <a href="!taxonomy_url">Taxonomy vocabularies</a> with <a href="!ooyala_url">Ooyala labels</a>. This can be useful to maintain useful information within both Ooyala Backlot and Drupal.', array('!taxonomy_url' => url('admin/content/taxonomy'), '!ooyala_url' => 'http://www.ooyala.com/www3/support_content_management#labels')),
+    '#access' => module_exists('taxonomy'),
+    '#tree' => TRUE,
+  );
+
+  $vocabularies = (array) module_invoke('taxonomy', 'get_vocabularies');
+  $api_active = ooyala_api_available();
+  $problems = array();
+
+  if (empty($vocabularies)) {
+    $problems[] = t('There are currently no Taxonomy vocabularies available to synchronize.');
+  }
+  if (!$api_active) {
+    $problems[] = t('The Ooyala API is currently not available. Check that your server can make HTTP requests.');
+  }
+  else {
+    $labels = ooyala_api_label_list('<root>');
+    if ($labels === FALSE) {
+      $problems[] = t('The Ooyala Parter and Secret code could not be validated (you may need to save this page).');
+    }
+    elseif (count($labels) == 0) {
+      $problems[] = t('Currently there are no labels defined within <a href="!ooyala_backlot">Ooyala Backlot</a>. At least one label needs to be created in order to synchronize it with a Taxonomy vocabulary.', array('!ooyala_backlot' => 'https://backlot.ooyala.com/backlot/web'));
+    }
+  }
+
+  if (empty($problems)) {
+    $form['ooyala_taxonomy']['#description'] .= ' ' . t('Each select list below represents a Drupal taxonomy. The items within each list are top-level Ooyala labels that have been pulled from Backlot.');
+    $options = array();
+    foreach ($labels as $olid => $label) {
+      $label = ltrim($label, '/');
+      $options[$olid] = $label;
+    }
+
+    $default_values = variable_get('ooyala_taxonomy', array());
+    foreach ($vocabularies as $vocabulary) {
+      $form['ooyala_taxonomy'][$vocabulary->vid] = array(
+        '#title' => $vocabulary->name,
+        '#type' => 'select',
+        '#options' => array_merge(array('' => t('-- No syncing -- ')), $options),
+        '#default_value' => isset($default_values[$vocabulary->vid]) ? $default_values[$vocabulary->vid] : '',
+      );
+    }
+    $form['ooyala_taxonomy_current'] = array(
+      '#type' => 'value',
+      '#value' => $default_values,
+    );
+  }
+  else {
+    $form['ooyala_taxonomy']['#description'] .= ' <strong>' . t('Taxonomy syncing cannot be enabled for the following reasons:') . '</strong>' . theme('item_list', $problems);
+  }
+
   $form['ooyala_reporting_level'] = array(
     '#type' => 'radios',
     '#title' => t('Reporting level'),
@@ -130,10 +186,56 @@
       '0' => t('Quiet - log errors to watchdog (recommended for production sites)'),
     ),
     '#default_value' => variable_get('ooyala_reporting_level', 1),
-    '#weight' => 10,
   );
 
-  return system_settings_form($form);
+  // Add the save button and normal submit handlers.
+  $form = system_settings_form($form);
+
+  // Add the additional submit handler needed for Taxonomy syncing.
+  if ($form['ooyala_taxonomy']['#access'] && empty($problems)) {
+    $form['#submit'][] = 'ooyala_settings_form_submit';
+  }
+
+  return $form;
+}
+
+/**
+ * Submit handler for ooyala_settings_form().
+ *
+ * Taxonomies need special form handling. All other settings are saved by the
+ * standard system_settings_form_submit() function.
+ */
+function ooyala_settings_form_submit($form, &$form_state) {
+  $existing_vocabularies = $form_state['values']['ooyala_taxonomy_current'];
+  $pending_vocabularies = array();
+  $new_vocabularies = array();
+  foreach ($form_state['values']['ooyala_taxonomy'] as $vid => $olid) {
+    if ($olid) {
+      // If syncing is already enabled, keep it enabled.
+      if (!empty($existing_vocabularies[$vid])) {
+        $new_vocabularies[$vid] = $olid;
+      }
+      // Syncing is being enabled for the first time.
+      else {
+        // If this vocabulary has terms, add it to the list for batch operation.
+        if (count(taxonomy_get_tree($vid, 0, -1, 1))) {
+          $pending_vocabularies[$vid] = $olid;
+        }
+        // If the vocabulary is empty, just enable syncing immediately.
+        else {
+          $new_vocabularies[$vid] = $olid;
+        }
+      }
+    }
+  }
+
+  // Set the taxonomy variable immediately for existing or empty vocabularies.
+  variable_set('ooyala_taxonomy', $new_vocabularies);
+
+  // Set the redirect to enable vocabularies that have existing terms.
+  if (!empty($pending_vocabularies)) {
+    $form_state['redirect'] = array('admin/settings/ooyala/sync', array('vids' => $pending_vocabularies));
+  }
 }
 
 /**
@@ -161,6 +263,53 @@
 }
 
 /**
+ * Menu callback; Batch process new vocabulary syncing.
+ */
+function ooyala_taxonomy_sync_confirm($form_state, $vids = array()) {
+  $form = array();
+
+  if (empty($vids) && isset($_GET['vids']) && is_array($_GET['vids'])) {
+    $vids = $_GET['vids'];
+  }
+
+  $vocabularies = taxonomy_get_vocabularies();
+  $vocabulary_list = array();
+  foreach ($vocabularies as $vocabulary) {
+    if (isset($vids[$vocabulary->vid])) {
+      $vocabulary_list[] = check_plain($vocabulary->name);
+    }
+  }
+
+  $form['vocabularies'] = array(
+    '#type' => 'value',
+    '#value' => $vids,
+  );
+
+  $question = t('Sync existing terms with Ooyala?');
+  $description = '<p>' . t("The vocabularies you've chosen to sync need to be populated into Ooyala Backlot. The Drupal \"term\" data will be stored as Ooyala \"labels\". If you cancel this action, syncing will not be enabled. The following vocabularies need to be synced:") . '</p>';
+  $description .= theme('item_list', $vocabulary_list);
+  $path = 'admin/settings/ooyala';
+  return confirm_form($form, $question, $path, $description);
+}
+
+function ooyala_taxonomy_sync_confirm_submit($form, &$form_state) {
+  $batch = array(
+    'operations' => array(),
+    'finished' => 'ooyala_taxonomy_term_sync_push_finished',
+    'title' => t('Processing'),
+    'error_message' => t('The sync has encountered an error.'),
+    'file' => drupal_get_path('module', 'ooyala') .'/includes/ooyala.taxonomy.inc',
+  );
+
+  foreach ($form_state['values']['vocabularies'] as $vid => $olid) {
+    $batch['operations'][] = array('ooyala_taxonomy_term_sync_push', array($vid, $olid));
+  }
+
+  batch_set($batch);
+  $form_state['redirect'] = 'admin/settings/ooyala';
+}
+
+/**
  * Menu callback; Return AJAX data that will be sent to Ooyala on upload.
  */
 function ooyala_upload_js($thisfield) {
@@ -254,6 +403,7 @@
  * Menu callback; Retrieve a new thumbnail from the Ooyala servers.
  */
 function ooyala_refresh_thumbnail() {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.api');
   $token = drupal_get_token();
   $data = array();
   if (isset($_GET['token']) && $_GET['token'] == $token) {
@@ -307,6 +457,7 @@
  * if Drupal is installed at the base of the domain.
  */
 function ooyala_ping() {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.api');
   $messages = array();
   $messages[] = t('Received ping from Ooyala Backlot.');
 
@@ -366,6 +517,8 @@
  * Menu callback to list all videos from Backlot for importing as nodes.
  */
 function ooyala_import_multiple(&$form_state) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.api');
+
   // If doing a confirmation form, short cut the rest of the process.
   if (isset($form_state['storage']['import'])) {
     $form = ooyala_import_confirm($form_state);
@@ -578,6 +731,9 @@
   // Create a node out of it.
   $node = ooyala_create_node($item, $type);
 
+  // Pull in the thumbnail for the new node.
+  ooyala_api_fetch_image($item['embedCode']);
+
   // Redirect to the node.
   $form_state['redirect'] = array('node/' . $node->nid);
 }
Index: includes/ooyala.taxonomy.inc
===================================================================
RCS file: includes/ooyala.taxonomy.inc
diff -N includes/ooyala.taxonomy.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/ooyala.taxonomy.inc	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,462 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Code responsible for checking and synchronizing labels with Ooyala.
+ */
+
+/**
+ * Cron callback.
+ */
+function ooyala_taxonomy_cron() {
+  // Record the time the sync started.
+  $sync_time = time();
+
+  // Running the sync can take a long time. Only process videos since last sync.
+  $last_sync = variable_get('ooyala_last_sync', 0);
+  ooyala_taxonomy_term_sync_pull($last_sync);
+
+  // Set the sync time so that we don't need to process unchanged videos.
+  variable_set('ooyala_last_sync', $sync_time);
+}
+
+/**
+ * Retrieve a list of all labels from Ooyala and check them for changes.
+ *
+ * Note that the Ooyala module immediately notifies Ooyala of changes on the
+ * Drupal side through hook_taxonomy() and hook_nodeapi(). Therfor we only need
+ * to pull from Ooyala on cron jobs, bulk sending of information is only
+ * necessary when initially setting up a vocabulary.
+ *
+ * @param $updated_after
+ *   Optional. Only update video label information for videos that were last
+ *   changed after this timestamp. Note that label changes are always updated
+ *   on every request, this only affects node-label relationships.
+ *
+ * @see ooyala_taxonomy_term_sync_push()
+ */
+function ooyala_taxonomy_term_sync_pull($updated_after = 0) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.api');
+
+  $ooyala_taxonomy = variable_get('ooyala_taxonomy', array());
+  $complete_map = array();
+  $vocabulary_labels = array();
+
+  // Update label information.
+  foreach ($ooyala_taxonomy as $vid => $vocabulary_olid) {
+    if (empty($vocabulary_olid)) {
+      continue;
+    }
+
+    // Save the vocabulary label equivelant for later.
+    $vocabulary_labels[$vid] = ooyala_api_label_name($vocabulary_olid);
+
+    // Get the list of labels, terms, and the term to label ID map.
+    $labels = ooyala_api_label_list($vocabulary_olid);
+    $terms = ooyala_taxonomy_term_list($vid);
+    $map = ooyala_taxonomy_term_map($vid);
+
+    // Syncronize information from Ooyala into Drupal.
+    foreach ($labels as $olid => $label_path) {
+      $tid = array_search($olid, $map);
+      $term = $tid ? $terms[$tid] : NULL;
+      $parent_names = explode('/', substr($label_path, 1));
+      $label_name = array_pop($parent_names);
+      $parent_path = $parent_names ? '/' . implode('/', $parent_names) : '/';
+      $parent_olid = $parent_path ? array_search($parent_path, $labels) : '';
+      $parent_tid = $parent_olid ? array_search($parent_olid, $map) : 0;
+      $parent_term = $parent_tid ? $terms[$tid] : NULL;
+
+      // If the label does not yet have a taxonomy term, create one.
+      if ($tid == FALSE) {
+        $term = array(
+          'vid' => $vid,
+          'name' => $label_name,
+          'parent' => $parent_tid,
+          'olid' => $olid,
+        );
+        $term = (object) taxonomy_save_term($term);
+        $tid = $term->tid;
+        $terms[$tid] = $term;
+        $map[$tid] = $olid;
+        $complete_map[$tid] = $olid;
+        watchdog('ooyala', 'Imported new term %name from Ooyala.', array('%name' => $term->name), WATCHDOG_NOTICE);
+      }
+      // The label already has a taxonomy term, check for changes.
+      else {
+        $original_term = drupal_clone($term);
+
+        // If the label has been renamed, update it on the Drupal side.
+        if ($label_name != $term->name) {
+          $term->name = $label_name;
+        }
+
+        // If the label has been moved, update the parent on the Drupal side.
+        if ($parent_tid != $term->pid) {
+          $term->pid = $parent_tid;
+        }
+
+        // Save any changes.
+        if ($original_term != $term) {
+          $term_array = (array) $term;
+          taxonomy_save_term($term_array);
+          $terms[$term->tid] = $term;
+        }
+        $complete_map[$tid] = $olid;
+      }
+    }
+
+    // Check for deleted labels and delete corresponding terms.
+    foreach ($map as $tid => $olid) {
+      if (!isset($complete_map[$tid])) {
+        taxonomy_del_term($tid);
+        watchdog('ooyala', 'Deleted term %name to match Ooyala.', array('%name' => $term->name), WATCHDOG_NOTICE);
+      }
+    }
+  }
+
+  // Make a regular expression that we can use to filter out non-synced labels.
+  foreach ($vocabulary_labels as $vid => $label) {
+    $vocabulary_labels[$vid] = preg_quote($label, '/');
+  }
+  $label_filter .= '/^' . implode('|', $vocabulary_labels) . '\//';
+
+  // Update video usage of labels within all vocabularies.
+  $videos = ooyala_api_video_query(array('updatedAfter' => $updated_after, 'orderBy' => 'updatedAt,asc', 'fields' => 'labels'));
+  foreach ($videos as $embedcode => $video) {
+    $nodes = ooyala_load_nodes($embedcode);
+    foreach ($nodes as $node) {
+      // On each node, get the list of current labels and compare against
+      // what Ooyala says each video is using.
+      $node_labels = ooyala_node_labels($node);
+      $ooyala_labels = array();
+      foreach ($video['labels'] as $olid => $label_name) {
+        // Do not compare labels that aren't synced with Drupal.
+        if (preg_match($label_filter, $label_name)) {
+          $ooyala_labels[] = $olid;
+        }
+      }
+
+      $diff1 = array_diff($node_labels, $ooyala_labels);
+      $diff2 = array_diff($ooyala_labels, $node_labels);
+
+      // If there is any difference, replace with the Ooyala labels.
+      if ($diff1 || $diff2) {
+        $term_list = array();
+        // Keep the list of terms that are not synced with Ooyala at all.
+        foreach ($node->taxonomy as $tid => $term) {
+          if (!isset($ooyala_taxonomy[$term->vid])) {
+            $term_list[] = $tid;
+          }
+        }
+        // Add the new Ooyala labels as terms.
+        foreach ($ooyala_labels as $olid) {
+          $term_list[] = array_search($olid, $complete_map);
+        }
+        $node->taxonomy = $term_list;
+        $node->pulled_from_ooyala = TRUE;
+        watchdog('ooyala', 'Updated !title @type to match changes from Ooyala.', array('!title' => l($node->title, 'node/' . $node->nid), '@type' => $node->type), WATCHDOG_NOTICE);
+        node_save($node);
+      }
+    }
+  }
+}
+
+/**
+ * Initial sync of Drupal taxonomy terms to Ooyala.
+ *
+ * This function is called when taxonomy syncing is first enabled. The normal
+ * sync routine assumes that Ooyala is always the most up-to-date source of
+ * labels (since Drupal immediately informs Ooyala of changes). However when
+ * first enabling a vocabulary for syncing, Drupal needs to populate Ooyala
+ * with labels.
+ *
+ * @param $vid
+ *   The Drupal vocabulary to send to Ooyala as labels.
+ * @param $olid
+ *   The Ooyala label ID for the vocabulary.
+ * @param $context
+ *   The Batch API variable containing information about how many terms and
+ *   nodes remain to be processed.
+ *
+ * @see ooyala_taxonomy_term_sync_pull()
+ */
+function ooyala_taxonomy_term_sync_push($vid, $olid, &$context) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.api');
+
+  if (!isset($context['sandbox']['progress'])) {
+    $context['sandbox']['vocabulary'] = taxonomy_vocabulary_load($vid);
+    $context['sandbox']['terms'] = taxonomy_get_tree($vid);
+    $context['sandbox']['max'] = count($context['sandbox']['terms']);
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['term_map'] = array();
+    foreach ($context['sandbox']['terms'] as $index => $term) {
+      $context['sandbox']['term_map'][$term->tid] = $index;
+    }
+
+    // Add this vocabulary to the list of results.
+    $context['results']['vocabularies'][$vid] = $olid;
+  }
+
+  // Process terms by groups of 10.
+  $count = 10;
+  $terms = $context['sandbox']['terms'];
+  $term_map = $context['sandbox']['term_map'];
+  $current = $context['sandbox']['progress'];
+  $vocabulary = $context['sandbox']['vocabulary'];
+  $end = min($current + $count, $context['sandbox']['max']);
+  for ($current; $current <= $end ; $current++) {
+    $term = $terms[$current];
+
+    // Generate a label path.
+    $label = ooyala_taxonomy_create_label($term, $vid, $olid);
+
+    // Send the term to Ooyala and retrieve a label ID.
+    $term->olid = ooyala_api_label_add($label);
+    ooyala_taxonomy_term_save($term);
+
+    // Store result for post-processing in the finished callback.
+    $context['results']['terms'][] = $term->tid;
+
+    // Update our progress information.
+    $context['sandbox']['progress'] = $current;
+    $context['message'] = t('Processing %title', array('%title' => $node->title));
+  }
+
+  // Inform the batch engine that we are not finished,
+  // and provide an estimation of the completion level we reached.
+  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+  }
+}
+
+/**
+ * Batch API finished callback. Clean up the push operation and enable.
+ */
+function ooyala_taxonomy_term_sync_push_finished($success, $results, $operations) {
+  $message = format_plural(count($results['terms']), '1 taxonomy term sent to Ooyala.', '@count taxonomy terms sent to Ooyala.');
+
+  if ($success) {
+    // Enable syncing for the vocabulary.
+    $ooyala_taxonomy = variable_get('ooyala_taxonomy', array());
+    foreach ($results['vocabularies'] as $vid => $olid) {
+      $ooyala_taxonomy[$vid] = $olid;
+    }
+    variable_set('ooyala_taxonomy', $ooyala_taxonomy);
+    $message = t('All content has been succesfully synced with Ooyala.') . ' ' . $message;
+    drupal_set_message($message);
+  }
+  else {
+    $message = t('An error occurred and synchronization did not complete.') . ' ' . $message;
+    drupal_set_message($message, 'error');
+  }
+}
+
+/**
+ * Load a list of Drupal taxonomy terms within a vocabulary.
+ */
+function ooyala_taxonomy_term_list($vid = NULL, $reset = FALSE) {
+  static $terms;
+
+  if (!isset($terms) || $reset) {
+    $terms = array();
+  }
+
+  if ($vid && !isset($terms[$vid])) {
+    $result = db_query("SELECT t.*, ot.olid, ot.updated FROM {term_data} t LEFT JOIN {ooyala_term} ot ON t.tid = ot.tid WHERE t.vid = %d", $vid);
+    $terms[$vid] = array();
+    while ($term = db_fetch_object($result)) {
+      $terms[$vid][$term->tid] = $term;
+    }
+
+    // Add parents to each term after building the full list.
+    foreach ($terms[$vid] as $tid => $term) {
+      if ($term->pid && isset($terms[$vid][$term->pid])) {
+        $parent = $terms[$vid][$term->pid];
+        while ($parent->pid) {
+          $terms[$vid][$tid]->parents[] = $parent;
+          if ($terms[$vid][$parent->pid]) {
+            $parent = $terms[$vid][$parent->pid];
+          }
+        }
+      }
+    }
+  }
+
+  return $vid ? $terms[$vid] : TRUE;
+}
+
+/**
+ * Provide a term ID to Ooyala ID map.
+ */
+function ooyala_taxonomy_term_map($vid) {
+  $terms = ooyala_taxonomy_term_list($vid);
+  $map = array();
+  foreach ($terms as $tid => $term) {
+    $map[$tid] = $term->olid;
+  }
+  return $map;
+}
+
+/**
+ * Load an individual Drupal taxonomy term, including Ooyala Label ID (olid).
+ */
+function ooyala_taxonomy_term_load($tid) {
+  $result = db_query("SELECT * FROM {term_data} t LEFT JOIN {ooyala_term} ot ON t.tid = ot.tid WHERE t.tid = %d", $tid);
+  return db_fetch_object($result);
+}
+
+/**
+ * Implementation of hook_taxonomy_term_insert().
+ *
+ * Called from hook_taxonomy() in ooyala.module.
+ */
+function ooyala_taxonomy_term_insert($term) {
+  $ooyala_vocabularies = variable_get('ooyala_taxonomy', array());
+  if (!empty($ooyala_vocabularies[$term->vid])) {
+    $vocabulary_olid = $ooyala_vocabularies[$term->vid];
+    // Check if needing to add this term to Ooyala. If $term->olid is already
+    // set this term originated from Ooyala and doesn't need to be sent.
+    // See ooyala_taxonomy_term_sync().
+    if (!isset($term->olid)) {
+      module_load_include('inc', 'ooyala', 'includes/ooyala.api');
+      $label = ooyala_taxonomy_create_label($term, $term->vid, $vocabulary_olid);
+      if ($olid = ooyala_api_label_add($label)) {
+        $term->olid = $olid;
+      }
+    }
+    // Always add the entry in the ooyala_term table.
+    if (isset($term->olid)) {
+      ooyala_taxonomy_term_save($term);
+    }
+  }
+}
+
+/**
+ * Implementation of hook_taxonomy_term_update().
+ *
+ * Called from hook_taxonomy() in ooyala.module.
+ */
+function ooyala_taxonomy_term_update($term) {
+  $ooyala_vocabularies = variable_get('ooyala_taxonomy', array());
+  if ($ooyala_vocabularies[$term->vid]) {
+    $vocabulary_olid = $ooyala_vocabularies[$term->vid];
+
+    // Load the Ooyala label ID and check for renamed terms.
+    $term = ooyala_taxonomy_term_load($term->tid);
+    if (isset($term->olid)) {
+      module_load_include('inc', 'ooyala', 'includes/ooyala.api');
+      $old_label = ooyala_api_label_name($term->olid);
+      $label = ooyala_taxonomy_create_label($term, $term->vid, $vocabulary_olid);
+      if ($old_label != $label) {
+        ooyala_api_label_rename($old_label, $label);
+        ooyala_taxonomy_term_save($term);
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of hook_taxonomy_term_delete().
+ *
+ * Called from hook_taxonomy() in ooyala.module.
+ */
+function ooyala_taxonomy_term_delete($term) {
+  $ooyala_vocabularies = variable_get('ooyala_taxonomy', array());
+  if ($ooyala_vocabularies[$term->vid]) {
+    // Delete any matching Ooyala label information. We have to use a direct
+    // query here instead of ooyala_taxonomy_term_load() because the term_data
+    // record has already been deleted.
+    $olid = db_result(db_query("SELECT olid FROM {ooyala_term} ot WHERE ot.tid = %d", $term->tid));
+    if ($olid) {
+      module_load_include('inc', 'ooyala', 'includes/ooyala.api');
+      ooyala_api_label_delete($olid);
+      db_query("DELETE FROM {ooyala_term} WHERE tid = %d", $term->tid);
+    }
+  }
+}
+
+/**
+ * Save a taxonomy term's Ooyala information to the database.
+ *
+ * This function is called from ooyala_taxonomy_term_insert() and
+ * ooyala_taxonomy_term_update().
+ */
+function ooyala_taxonomy_term_save($term) {
+  static $time;
+  $time = isset($time) ? $time : time();
+
+  $update = !isset($term->updated) ? NULL : array('tid');
+  $term->updated = $time;
+  $term->olid = isset($term->olid) ? $term->olid : NULL;
+
+  if ($term->tid && $term->olid) {
+    drupal_write_record('ooyala_term', $term, $update);
+  }
+}
+
+/**
+ * Implementation of hook_taxonomy_vocabulary_delete().
+ *
+ * Called from hook_taxonomy() in ooyala.module.
+ */
+function ooyala_taxonomy_vocabulary_delete($vocabulary) {
+  $ooyala_vocabularies = variable_get('ooyala_taxonomy', array());
+  if ($ooyala_vocabularies[$vocabulary->vid]) {
+    unset($ooyala_vocabularies[$vocabulary->vid]);
+    variable_set('ooyala_taxonomy', $ooyala_vocabularies);
+    db_query('DELETE FROM {ooyala_term} WHERE vid = %d', $vocabulary->vid);
+    drupal_set_message(t('The vocabulary %name is no longer synced with Ooyala. Labels within Ooyala backlot have not been affected.', array('%name' => $vocabulary->name)));
+  }
+}
+
+/**
+ * Given a taxonomy term or vocabulary, create an Ooyala label.
+ */
+function ooyala_taxonomy_create_label($term = NULL, $vid = NULL, $vocabulary_olid = NULL) {
+  static $vocabulary_labels;
+
+  // Get the VID from the term if needed.
+  if (!isset($vid)) {
+    $vid = $term->vid;
+  }
+
+  // If an Ooyala label ID is not known for the vocabulary, look it up from the
+  // saved Ooyala taxonomy settings.
+  if (!isset($vocabulary_olid)) {
+    $ooyala_taxonomy = variable_get('ooyala_taxonomy', array());
+    $vocabulary_olid = isset($ooyala_taxonomy[$vid]) ? $ooyala_taxonomy[$vid] : NULL;
+  }
+
+  // Get the root label list from Ooyala, where we can lookup the label name.
+  if (!isset($vocabulary_labels)) {
+    module_load_include('inc', 'ooyala', 'includes/ooyala.api');
+    $vocabulary_labels = ooyala_api_label_list('<root>');
+  }
+
+  // Set the label name based on the Ooyala label ID.
+  $label = $vocabulary_labels[$vocabulary_olid];
+
+  if (empty($label)) {
+    return FALSE;
+  }
+
+  if (isset($term)) {
+    // Build a list of parents for this term if needed.
+    $parents = isset($term->parents) ? $term->parents : taxonomy_get_parents($term->tid);
+
+    // Tag vocabularies for some reason are given a single parent of "0". Remove.
+    if (isset($parents[0]) && $parents[0] === '0') {
+      $parents = array();
+    }
+
+    // Assemble the rest of the label name from the parents and term name.
+    foreach ($parents as $parent_term) {
+      $label .= '/' . $parent_term->name;
+    }
+    $label .= '/' . $term->name;
+  }
+
+  return $label;
+}
Index: ooyala.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ooyala/ooyala.install,v
retrieving revision 1.2
diff -u -r1.2 ooyala.install
--- ooyala.install	21 Oct 2010 17:39:34 -0000	1.2
+++ ooyala.install	14 Dec 2010 18:31:38 -0000
@@ -7,9 +7,55 @@
  */
 
 /**
+ * Implementation of hook_schema().
+ */
+function ooyala_schema() {
+  $schema = array();
+
+  $schema['ooyala_term'] = array(
+    'description' => 'Table for storing Ooyala label to Drupal term ID pairs',
+    'fields' => array(
+      'vid' => array(
+        'description' => 'Foreign key to the vid of the {vocabulary} table.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'tid' => array(
+        'description' => 'Foreign key to the tid of the {term_data} table.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'olid' => array(
+        'description' => 'The label ID provided by Ooyala',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'updated' => array(
+        'description' => 'The last time that the matching Drupal term was updated.',
+        'type' => 'int',
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array('vid', 'tid', 'olid'),
+    'indexes' => array(
+      'updated' => array('updated'),
+    ),
+  );
+
+  return $schema;
+}
+
+/**
  * Implementation of hook_install().
  */
 function ooyala_install() {
+  drupal_install_schema('ooyala');
   drupal_load('module', 'content');
   content_notify('install', 'ooyala');
 }
@@ -18,6 +64,7 @@
  * Implementation of hook_uninstall().
  */
 function ooyala_uninstall() {
+  drupal_uninstall_schema('ooyala');
   drupal_load('module', 'content');
   content_notify('uninstall', 'ooyala');
 }
@@ -88,3 +135,51 @@
 
   return $ret;
 }
+
+/**
+ * Install the new initial Ooyala schema.
+ */
+function ooyala_update_6002() {
+  $ret = array();
+  $schema = array();
+
+  $schema['ooyala_term'] = array(
+    'description' => 'Table for storing Ooyala label to Drupal term ID pairs',
+    'fields' => array(
+      'vid' => array(
+        'description' => 'Foreign key to the vid of the {vocabulary} table.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'tid' => array(
+        'description' => 'Foreign key to the tid of the {term_data} table',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'olid' => array(
+        'description' => 'The label ID provided by Ooyala',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'updated' => array(
+        'description' => 'The last time that the matching Drupal term was updated.',
+        'type' => 'int',
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array('vid', 'tid', 'olid'),
+    'indexes' => array(
+      'updated' => array('updated'),
+    ),
+  );
+
+  db_create_table($ret, 'ooyala_term', $schema['ooyala_term']);
+
+  return $ret;
+}
Index: ooyala.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ooyala/ooyala.module,v
retrieving revision 1.34
diff -u -r1.34 ooyala.module
--- ooyala.module	13 Dec 2010 03:57:25 -0000	1.34
+++ ooyala.module	14 Dec 2010 18:31:39 -0000
@@ -9,8 +9,6 @@
  * upload video directly to the Ooyala service and display it.
  */
 
-module_load_include('inc', 'ooyala', 'includes/ooyala.api');
-
 /**
  * Implementation of hook_menu().
  */
@@ -40,6 +38,15 @@
     'file' => 'includes/ooyala.pages.inc',
     'type' => MENU_NORMAL_ITEM,
   );
+  $items['admin/settings/ooyala/sync'] = array(
+    'title' => 'Sync confirmation',
+    'description' => 'Confirmation page before beginning taxonomy syncing.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('ooyala_taxonomy_sync_confirm'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'includes/ooyala.pages.inc',
+    'type' => MENU_CALLBACK,
+  );
   $items['ooyala/js'] = array(
     'page callback' => 'ooyala_upload_js',
     'access arguments' => array('upload ooyala videos'),
@@ -200,13 +207,17 @@
  * Menu loader for getting all video data from Ooyala based on an embed code.
  */
 function ooyala_item_load($embedcode) {
-  return ooyala_api_video_load($embedcode);
+  if (!empty($embedcode) && strlen($embedcode) == 32) {
+    module_load_include('inc', 'ooyala', 'includes/ooyala.api');
+    return ooyala_api_video_load($embedcode);
+  }
 }
 
 /**
  * Implementation of hook_cron().
  */
 function ooyala_cron() {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.api');
   if (!ooyala_api_available()) {
     return;
   }
@@ -225,6 +236,12 @@
 
   variable_set('ooyala_pending_thumbnails', $thumbnails_to_get);
 
+  // Sync Taxonomy terms.
+  if (module_exists('taxonomy') && variable_get('ooyala_taxonomy', array())) {
+    module_load_include('inc', 'ooyala', 'includes/ooyala.taxonomy');
+    ooyala_taxonomy_cron();
+  }
+
   // Update the status/length information for any nodes that haven't had the
   // data synced yet. We currently procerss 10 nodes per cron run to avoid
   // timeout.
@@ -284,14 +301,15 @@
  *   Array of updated node titles indexed by node id.
  */
 function _ooyala_sync_update_nodes($nodes) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.api');
   if (!ooyala_api_available()) {
     return FALSE;
   }
 
   // Query the Ooyala API and save retrieved information for each node.
   $updated = array();
-  foreach ($nodes as $key => $value) {
-    $node = is_object($value) ? $value : node_load($value);
+  foreach ($nodes as $node) {
+    $node = is_object($node) ? $node : node_load($node);
     foreach ($node->ooyala_field_names as $field_name) {
       foreach ($node->{$field_name} as $key => $value) {
         $embedcode = isset($node->{$field_name}[$key]['value']) ? $node->{$field_name}[$key]['value'] : FALSE;
@@ -339,6 +357,33 @@
         }
       }
       break;
+    case 'presave':
+      $ooyala_taxonomy = variable_get('ooyala_taxonomy', array());
+      // This special flag is set when we're pulling information from Ooyala
+      // on cron jobs. No point in sending information back to Ooyala that we
+      // just got from them.
+      if (!isset($node->pulled_from_ooyala) && $ooyala_taxonomy) {
+        $labels = ooyala_node_labels($node);
+        $embedcodes = ooyala_node_embedcodes($node);
+
+        // Remove and re-add all existing labels for synced vocabularies.
+        ooyala_api_video_label_remove($embedcodes, $ooyala_taxonomy);
+        ooyala_api_video_label_add($embedcodes, $labels);
+      }
+      break;
+  }
+}
+
+/**
+ * Implementation of hook_taxonomy().
+ */
+function ooyala_taxonomy($op, $type, $array = NULL) {
+  // Taxonomy hooks are broken out separately in ooyala.taxonomy.inc.
+  module_load_include('inc', 'ooyala', 'includes/ooyala.taxonomy');
+  $function = 'ooyala_taxonomy_' . $type . '_' . $op;
+  if (function_exists($function)) {
+    $object = (object) $array;
+    $function($object);
   }
 }
 
@@ -416,6 +461,7 @@
     case 'insert':
     case 'udpate':
       // Attempt to retrieve video status/length data before saving the field.
+      module_load_include('inc', 'ooyala', 'includes/ooyala.api');
       foreach ($items as $delta => $item) {
         $items[$delta]['length'] = empty($items[$delta]['length']) ? ooyala_api_video_property($item['value'], 'length') : $items[$delta]['length'];
         $items[$delta]['status'] = empty($items[$delta]['status']) ? ooyala_api_video_property($item['value'], 'status') : $items[$delta]['status'];
@@ -522,6 +568,8 @@
  * Element #process function for the "ooyala_upload" field.
  */
 function ooyala_upload_process($element, $edit, &$form_state, $form) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.api');
+
   $field_name = $element['#field_name'];
   $field = $form['#field_info'][$field_name];
 
@@ -614,7 +662,7 @@
     $content_type = content_types($type_name);
     foreach ($content_type['fields'] as $field) {
       if ($field['type'] == 'ooyala') {
-        $field_names[] = $field['field_name'];
+        $field_names[$type_name][] = $field['field_name'];
       }
     }
   }
@@ -639,6 +687,87 @@
 }
 
 /**
+ * Fetch a list of all Ooyala labels that can be derived from a node's taxonomy.
+ *
+ * This function is used when saving a node and Taxonomy sycnronization is
+ * enabled. The list of labels is then compared against the Ooyala Backlot video
+ * and labels are added/removed to correspond with the synced vocabularies.
+ *
+ * @param $node
+ *   A full node object.
+ */
+function ooyala_node_labels($node) {
+  $labels = array();
+
+  $ooyala_vocabularies = array_filter(variable_get('ooyala_taxonomy', array()));
+  if (!module_exists('taxonomy') || empty($node->taxonomy) || empty($ooyala_vocabularies)) {
+    return $labels;
+  }
+
+  module_load_include('inc', 'ooyala', 'includes/ooyala.api');
+  module_load_include('inc', 'ooyala', 'includes/ooyala.taxonomy');
+
+  foreach ($ooyala_vocabularies as $vid => $olid) {
+    $vocabulary_label = ooyala_api_label_name($olid);
+
+    // Tags are simple, since they don't need to match Drupal term IDs.
+    if (isset($node->taxonomy['tags'][$vid])) {
+      $terms = drupal_explode_tags($node->taxonomy['tags'][$vid]);
+      foreach ($terms as $term) {
+        $labels[] = $vocabulary_label . '/' . $term;
+      }
+    }
+
+    // Normal controlled vocabularies, load Ooyala label IDs for each term.
+    $terms = array();
+
+    // Node form taxonomy format.
+    if (isset($node->taxonomy[$vid])) {
+      // Array form for multiple values.
+      if (is_array($node->taxonomy[$vid])) {
+        foreach ($node->taxonomy[$vid] as $tid) {
+          if ($tid) {
+            $terms[$tid] = ooyala_taxonomy_term_load($tid);
+          }
+        }
+      }
+      // Single value form.
+      elseif (is_numeric($node->taxonomy[$vid])) {
+        $tid = $node->taxonomy[$vid];
+        $terms[$tid] = ooyala_taxonomy_term_load($tid);
+      }
+    }
+
+    // Node object format.
+    foreach ($node->taxonomy as $term) {
+      if (is_object($term) && $term->vid == $vid) {
+        $terms[$term->tid] = ooyala_taxonomy_term_load($term->tid);
+      }
+    }
+
+    // Loop through all terms and build a list of labels.
+    foreach ($terms as $tid => $term) {
+      // Use the label ID if available, as it tends to be more accurate.
+      if (isset($term->olid)) {
+        $labels[] = $term->olid;
+      }
+      // Otherwise return a label as a path, assume it will be inserted.
+      else {
+        $parents = taxonomy_get_parents($tid);
+        $term_label = $vocabulary_label;
+        foreach ($parents as $parent_term) {
+          $term_label .= '/' . $parent_term->name;
+        }
+        $term_label .= '/' . $term->name;
+        $labels[] = $term_label;
+      }
+    }
+  }
+
+  return $labels;
+}
+
+/**
  * Report an error to the user.
  */
 function ooyala_message($message, $variables = array(), $type = 'status') {
@@ -1019,21 +1148,34 @@
  * Create a node out of the ooyala data passed in.
  *
  * @param array $item
- *   Array with at least the title, embedcode, status and length keys.
+ *   Array with at least the title, embedcode, status, and length keys.
  * @param string $content_type
  *   Machine name of the content type we're creating.
  */
 function ooyala_create_node($item, $content_type) {
-  // @todo: is there a cck function to get the field name?
-  $sql = "SELECT field_name FROM {content_node_field_instance} WHERE type_name = '%s' AND widget_module = 'ooyala'";
-  $field_name = db_result(db_query($sql, $content_type));
+  // Load the Ooyala information into the first field found in the content type.
+  $field_names = ooyala_field_names($content_type);
+  $field_name = array_shift($field_names);
+  if (!$field_name) {
+    return FALSE;
+  }
+
+  module_load_include('inc', 'node', 'node.pages');
+
   $edit = array();
   $edit['type'] = $content_type;
   $edit['title'] = $item['title'];
   $edit[$field_name][0]['value'] = $item['embedCode'];
   $edit[$field_name][0]['status'] = $item['status'];
   $edit[$field_name][0]['length'] = $item['length'];
+
+dsm($item);
+  foreach ($item['labels'] as $olid => $label) {
+    
+  }
+
   $node = node_submit($edit);
+  node_object_prepare($node);
   node_save($node);
   return $node;
 }
