diff --git includes/ooyala.pages.inc includes/ooyala.pages.inc
index c6b8f05..a17137d 100644
--- includes/ooyala.pages.inc
+++ includes/ooyala.pages.inc
@@ -105,6 +105,56 @@ function ooyala_settings_form(&$form_state) {
     '#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 (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)) {
+    $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'),
@@ -116,7 +166,48 @@ function ooyala_settings_form(&$form_state) {
     '#weight' => 10,
   );
 
-  return system_settings_form($form);
+  $form = system_settings_form($form);
+  $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' => array_keys($pending_vocabularies)));
+  }
 }
 
 /**
@@ -144,6 +235,74 @@ function ooyala_settings_file_validate($element) {
 }
 
 /**
+ * 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'];
+  }
+
+  $form['vocabularies'] = array(
+    '#type' => 'value',
+    '#value' => $vids,
+  );
+
+  $vocabularies = taxonomy_get_vocabularies();
+  $vocabulary_list = array();
+  foreach ($vocabularies as $vocabulary) {
+    if (in_array($vocabulary->vid, $vids)) {
+      $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_sync_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) {
+    $batch['operations'][] = array('ooyala_taxonomy_term_sync_push', array($vid));
+  }
+
+  batch_set($batch);
+  $form_state['redirect'] = 'admin/settings/ooyala';
+}
+
+/**
+ * Finished callback used after the initial Taxonomy sync is complete.
+ */
+function ooyala_taxonomy_sync_finished($success, $results, $operations) {
+  $message = format_plural(count($results), '1 video successfully processed.', '@count videos successfully processed.');
+
+  if ($success) {
+    $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');
+  }
+}
+
+/**
  * Menu callback; Return AJAX data that will be sent to Ooyala on upload.
  */
 function ooyala_upload_js($thisfield) {
diff --git ooyala.install ooyala.install
index c3cd0fd..e5e685f 100755
--- ooyala.install
+++ ooyala.install
@@ -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 @@ function ooyala_install() {
  * Implementation of hook_uninstall().
  */
 function ooyala_uninstall() {
+  drupal_uninstall_schema('ooyala');
   drupal_load('module', 'content');
   content_notify('uninstall', 'ooyala');
 }
@@ -39,6 +86,54 @@ function ooyala_disable() {
 }
 
 /**
+ * Install the initial Ooyala schema.
+ */
+function ooyala_update_6000() {
+  $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;
+}
+
+/**
  * Add *_status and *_length columns for all ooyala video fields.
  */
 function ooyala_update_6001() {
diff --git ooyala.module ooyala.module
index d3012fa..b990268 100755
--- ooyala.module
+++ ooyala.module
@@ -22,6 +22,15 @@ function ooyala_menu() {
     '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'),
@@ -197,6 +206,12 @@ function ooyala_cron() {
 
   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_term_sync_pull();
+  }
+
   // 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.
@@ -208,7 +223,7 @@ function ooyala_cron() {
 }
 
 /**
- * Query Ooyala API to sync status/length information for nodes that are
+ * Query Ooayal API to sync status/length information for nodes that are
  * missing that information.
  *
  * @param $limit
@@ -311,6 +326,32 @@ function ooyala_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
         }
       }
       break;
+    case 'presave':
+      $labels = ooyala_node_labels($node);
+      $embedcodes = ooyala_node_embedcodes($node);
+      if ($labels) {
+        // Remove all existing labels for synced vocabularies.
+        ooyala_api_video_label_remove($embedcodes, array_keys($labels));
+        $all_labels = array();
+        foreach ($labels as $group) {
+          $all_labels = array_merge($all_labels, $group);
+        }
+        ooyala_api_video_label_add($embedcodes, $all_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);
   }
 }
 
@@ -585,6 +626,85 @@ function ooyala_node_embedcodes($node) {
 }
 
 /**
+ * Fetch a list of all Ooyala labels on a node that should be synced.
+ *
+ * @param $node
+ *   A full node object.
+ * @param $mode
+ *   Either "id" or "name", the format in which labels are preferred.
+ */
+function ooyala_node_labels($node, $restrict_vid = NULL) {
+  $labels = array();
+
+  $ooyala_vocabularies = array_filter(variable_get('ooyala_taxonomy', array()));
+  if (!module_exists('taxonomy') || !isset($node->taxonomy) || empty($ooyala_vocabularies)) {
+    return $labels;
+  }
+
+  module_load_include('inc', 'ooyala', 'includes/ooyala.taxonomy');
+  $vocabularies = taxonomy_get_vocabularies();
+  if (isset($restrict_vid)) {
+    $ooyala_vocabularies = array_intersect_key($ooyala_vocabularies, array($restrict_vid => ''));
+  }
+
+  foreach ($ooyala_vocabularies as $vid => $olid) {
+    $ooyala_label = ooyala_api_label_name($olid);
+
+    $labels[$ooyala_label] = array();
+
+    // 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[$ooyala_label][] = $ooyala_label . '/' . $term;
+      }
+    }
+
+    // Normal controlled vocabularies, load Ooyala label IDs for each term.
+    $terms = array();
+
+    // Node form taxonomy format.
+    if (isset($node->taxonomy[$vid])) {
+      if (is_array($node->taxonomy[$vid])) {
+        foreach ($node->taxonomy[$vid] as $tid) {
+          if ($tid) {
+            $terms[$tid] = ooyala_taxonomy_term_load($tid);
+          }
+        }
+      }
+    }
+
+    // Node object format.
+    foreach ($node->taxonomy as $term) {
+      if (is_object($term) && $term->vid == $vid) {
+        $terms[] = ooyala_taxonomy_term_load($term->tid);
+      }
+    }
+
+    // Loop through all terms and build a list of labels.
+    foreach ($terms as $tid => $term) {
+      if (is_object($term)) {
+        // Use the label ID if available, as it tends to be more accurate.
+        if (isset($term->olid)) {
+          $labels[$ooyala_label][$term->tid] = $term->olid;
+        }
+        // Otherwise return a label as a path, assume it will be inserted.
+        else {
+          $parents = taxonomy_get_parents($tid);
+          $labels[$ooyala_label] = '';
+          foreach ($parents as $parent_term) {
+            $labels[$ooyala_label] .= '/' . $parent_term->name;
+          }
+          $labels[$ooyala_label] .= '/' . $term->name;
+        }
+      }
+    }
+  }
+
+  return (isset($restrict_vid) && isset($ooyala_label)) ? $labels[$ooyala_label] : $labels;
+}
+
+/**
  * Check if Ooyala APIs are available.
  */
 function ooyala_api_available() {
@@ -597,6 +717,165 @@ function ooyala_api_available() {
 }
 
 /**
+ * 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, strrpos('/', $label_name) + 1);
+  $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;
+}
+
+/**
+ * Retreive 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.
+ * @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($labels[$parent])) {
+    $labels[$parent] = array();
+
+    // Query for any new videos that need to be retrieved.
+    module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
+
+    $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)) {
+      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 $labels[$parent];
+}
+
+/**
+ * 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) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
+
+  $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) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
+
+  $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) {
+  // TODO: Ooyala APIs currently do not provide a way to delete labels.
+  return TRUE;
+
+  module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
+
+  $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+  $request = $ooyala_api->signed_params(array(
+    'mode' => 'deleteLabels', // TODO: Correct when this becomes available.
+    '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;
+}
+
+/**
  * Retreive a non-cached list of videos from Ooyala.
  *
  * @param $params
@@ -709,6 +988,60 @@ function ooyala_api_video_property($embedcode, $property) {
 }
 
 /**
+ * Remove labels from a video.
+ */
+function ooyala_api_video_label_remove($embedcode, $labels, $recursive = TRUE) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
+
+  $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) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
+
+  $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;
+}
+
+/**
  * Fetch and save a video thumbnail from the Ooyala server.
  */
 function ooyala_api_fetch_image($embedcode) {
@@ -1136,6 +1469,17 @@ function ooyala_thickbox_player_page($embedcode) {
 }
 
 /**
+ * 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;
+}
+
+/**
  * Utility function for eliminating the need to have the "text" module activated
  */
 if (!function_exists('_text_allowed_values')) {
