<?php
// $Id: file_audio.module,v 1.20 2009/04/10 13:26:53 miglius Exp $

/**
 * @file
 * Supports audio file formats.
 */

//////////////////////////////////////////////////////////////////////////////

define('FILE_AUDIO_GETID3',     variable_get('file_audio_getid3', 1));
define('FILE_AUDIO_FLASH_LINK', 'http://www.adobe.com/products/flashplayer');

//////////////////////////////////////////////////////////////////////////////
// Core API hooks

/**
 * Implementation of hook_theme().
 */
function file_audio_theme() {
  return array(
    'file_audio_admin_settings' => array(
      'arguments' => array('form' => NULL),
      'file' => 'file_audio.theme.inc'
    ),
    'file_audio_mp3_render' => array(
      'arguments' => array('options' => NULL),
      'file' => 'file_audio.theme.inc'
    ),
  );
}

/**
 * Implementation of hook_menu().
 */
function file_audio_menu() {
  return array(
    'admin/settings/file/format/audio' => array(
      'title' => 'Audio',
      'description' => 'Manage the audio files.',
      'page callback' => 'drupal_get_form',
      'page arguments' => array('file_audio_admin_settings'),
      'access arguments' => array('administer site configuration'),
      'file' => 'file_audio.admin.inc',
    ),
    'file_audio/xspf' => array(
      'title' => 'Play list',
      'type' => MENU_CALLBACK,
      'page callback' => 'file_audio_xspf',
      'page arguments' => array(2, 3, 4),
      'access callback' => TRUE,
      'file' => 'file_audio.pages.inc',
    ),
  );
}

//////////////////////////////////////////////////////////////////////////////
// File API hooks

/**
 * Implementation of hook_mime_handlers().
 */
function file_audio_mime_handlers() {
  return array(
    'file_audio_mp3' => array(
      'name' => t('Audio MP3'),
      'dimensions' => '400x75',
    ),
  );
}

/**
 * Implementation of hook_mime_types().
 */
function file_audio_mime_types() {
  return array(
    'audio/basic' => array(
      'name' => t('NeXT/Sun audio file'),
    ),
    'audio/midi' => array(
      'name' => t('MIDI file'),
    ),
    'audio/mpeg' => array(
      'name' => t('MPEG-audio Layer 3 file'),
      'handlers' => array('file_audio_mp3'),
      'extensions' => array('mp3'), // to use as a first extension instead of mpga as defined in mime.type
    ),
    'audio/x-aiff' => array(
      'name' => t('AIFF audio file'),
    ),
    'audio/x-pn-realaudio' => array(
      'name' => t('RealAudio file'),
    ),
    'audio/x-pn-realaudio-plugin' => array(
      'name' => t('RealAudio file'),
      'extensions' => array('rpm'),
    ),
    'audio/x-realaudio' => array(
      'name' => t('RealAudio file'),
    ),
    'audio/vnd.rn-realaudio' => array(
      'name' => t('RealAudio file'),
      'extensions' => array('ra', 'ram'),
    ),
    'audio/x-wav' => array(
      'name' => t('Wave file'),
    ),
    'application/ogg' => array(
      'name' => t('Ogg Vorbis audio file'),
      'icon' => 'audio.gif',
    ),
  );
}

/**
 * Implementation of hook_mime_converters().
 */
function file_audio_mime_converters() {
  return array(
    'audio/basic' => array(
      'audio/mpeg' => array(
        'pipeline' => '{ffmpeg} -y -i "[in_file]" -ar 22050 "[out_file].mp3"; {mv} "[out_file].mp3" "[out_file]"',
        'handlers' => array('file_audio_mp3'),
      ),
    ),
    'audio/midi' => array(
      'audio/mpeg' => array(
        'pipeline' => '{ffmpeg} -y -i "[in_file]" -ar 22050 "[out_file].mp3"; {mv} "[out_file].mp3" "[out_file]"',
        'handlers' => array('file_audio_mp3'),
      ),
    ),
    'audio/x-aiff' => array(
      'audio/mpeg' => array(
        'pipeline' => '{ffmpeg} -y -i "[in_file]" -ar 22050 "[out_file].mp3"; {mv} "[out_file].mp3" "[out_file]"',
        'handlers' => array('file_audio_mp3'),
      ),
    ),
    'audio/x-pn-realaudio' => array(
      'audio/mpeg' => array(
        'pipeline' => '{ffmpeg} -y -i "[in_file]" -ar 22050 "[out_file].mp3"; {mv} "[out_file].mp3" "[out_file]"',
        'handlers' => array('file_audio_mp3'),
      ),
    ),
    'audio/x-pn-realaudio-plugin' => array(
      'audio/mpeg' => array(
        'pipeline' => '{ffmpeg} -y -i "[in_file]" -ar 22050 "[out_file].mp3"; {mv} "[out_file].mp3" "[out_file]"',
        'handlers' => array('file_audio_mp3'),
      ),
    ),
    'audio/x-realaudio' => array(
      'audio/mpeg' => array(
        'pipeline' => '{ffmpeg} -y -i "[in_file]" -ar 22050 "[out_file].mp3"; {mv} "[out_file].mp3" "[out_file]"',
        'handlers' => array('file_audio_mp3'),
      ),
    ),
    'audio/vnd.rn-realaudio' => array(
      'audio/mpeg' => array(
        'pipeline' => '{ffmpeg} -y -i "[in_file]" -ar 22050 "[out_file].mp3"; {mv} "[out_file].mp3" "[out_file]"',
        'handlers' => array('file_audio_mp3'),
      ),
    ),
    'audio/x-wav' => array(
      'audio/mpeg' => array(
        'pipeline' => '{ffmpeg} -y -i "[in_file]" -ar 22050 "[out_file].mp3"; {mv} "[out_file].mp3" "[out_file]"',
        'handlers' => array('file_audio_mp3'),
      ),
    ),
    'audio/ogg' => array(
      'audio/mpeg' => array(
        'pipeline' => '{ffmpeg} -y -i "[in_file]" -ar 22050 "[out_file].mp3"; {mv} "[out_file].mp3" "[out_file]"',
        'handlers' => array('file_audio_mp3'),
      ),
    ),
  );
}

/**
 * Implementation of hook_metadata_info().
 */
function file_audio_metadata_info() {
  $info = array();
  foreach (_file_audio_getid3() as $name => $data) {
    $info[$data['rdf']] = array('name' => t($data['name']));
    if (isset($data['theme']))
      $info[$data['rdf']]['theme'] = $data['theme'];
  }
  return $info;
}

/**
 * Implementation of hook_metadata_parse().
 */
function file_audio_metadata_parse($filename, $mimetype) {
  if (!array_key_exists($mimetype, file_audio_mime_types()))
    return NULL;

  $metadata = array();

  if (module_exists('getid3') && getid3_load()) {
    $info = getid3_analyze($filename);
  }
  else {
    $getid3_path = drupal_get_path('module', 'file') .'/vendor/getid3/getid3/getid3.php';
    if (!file_exists($getid3_path))
      return NULL;

    require_once $getid3_path;
    $id3 = new getID3;
    $info = @$id3->analyze($filename);
  }

  if (!empty($info['audio'])) {
    if (empty($info['audio']['codec']) && !empty($info['audio']['encoder']))
      $info['audio']['codec'] = $info['audio']['encoder'];

    $getid3 = _file_audio_getid3();
    $getid3_data_default = array();
    foreach ($getid3 as $name => $data) {
      $getid3_data_default[$name] = $data['default'];
    }
    $getid3_data = variable_get('file_audio_getid3_data', $getid3_data_default);

    $rdf_getid3 = array();
    foreach ($getid3 as $name => $data) {
      if (isset($getid3_data[$name]) && $element = file_get_recursive($info, $data['getid3'])) {
        switch ($data['type']) {
          case 'int' :
            $rdf_getid3[$data['rdf']] = array((int)$element);
            break;
          default :
            $rdf_getid3[$data['rdf']] = array($element);
            break;
        }
      }
    }
    $metadata = array_merge($rdf_getid3, $metadata);
  }

  return $metadata;
}

//////////////////////////////////////////////////////////////////////////////
// MIME handlers

/**
 * Generates an audio_mp3 preview.
 */
function file_audio_mp3_generate($file, $tier = NULL) {
  if (isset($tier) && !in_array($file->handlers[$tier], isset($file->previews[$file->mimes[$tier]]) ? $file->previews[$file->mimes[$tier]] : array())) {
    if (!file_get_converted($file, $tier))
      return FALSE;
    if (!file_data_save($file, $tier))
      return FALSE;
    watchdog('file_audio', 'The %mime preview was generated for the file %name, uri=%uri.', array('%mime' => $file->mimes[$tier], '%name' => $file->filename, '%uri' => $file->uri), WATCHDOG_NOTICE, isset($file->nid) ? l(t('view'), 'node/'. $file->nid) : NULL);
  }
  return TRUE;
}

/**
 * Implements an audio handler.
 */
function file_audio_mp3_render($uri, $options = array()) {
  $options = array_merge($options, _file_audio_render_options($uri, $options));
  return theme('file_audio_mp3_render', $options);
}

//////////////////////////////////////////////////////////////////////////////
// RDF API hooks

/**
 * Implementation of hook_rdf_namespaces().
 */
function file_audio_rdf_namespaces() {
  return array(
    //'foaf'    => 'http://xmlns.com/foaf/0.1/',
    'mo'      => 'http://purl.org/ontology/mo/',
    //'wordnet' => 'http://xmlns.com/wordnet/1.6/',
  );
}

//////////////////////////////////////////////////////////////////////////////
// Auxiliary functions

/**
 * Renders options for the audio display.
 */
function _file_audio_render_options($uri, $options = array()) {
  $node = !empty($options['vid']) ? node_load($options['nid'], $options['vid']) : NULL;
  $src = file_get_hash($uri) . (is_object($node) ? ('/'. $node->nid .'/'. $node->vid) : '');
  $title = isset($node->title) ? $node->title : '';

  $options = array_merge(array('class' => 'file-audio'), $options);
  return array_merge($options, compact('src', 'title'));
}

//////////////////////////////////////////////////////////////////////////////
// GetID3 settings

/**
 * getID3 to RDF mapping.
 */
function _file_audio_getid3() {
  return array(
    'audiodataformat'       => array('name' => 'Audio data format',        'getid3' => array('audio' => array('dataformat' => NULL)),                              'rdf' => 'wordnet:audio_dataformat',         'type' => '',      'default' => 1),
    'audiocodec'            => array('name' => 'Audio codec',              'getid3' => array('audio' => array('codec' => NULL)),                                   'rdf' => 'wordnet:audio_codec',              'type' => '',      'default' => 1),
    'audiobitrate'          => array('name' => 'Audio bitrate',            'getid3' => array('audio' => array('bitrate' => NULL)),                                 'rdf' => 'wordnet:audio_bitrate',            'type' => 'float', 'default' => 1),
    'audiobitratemode'      => array('name' => 'Audio bitrate mode',       'getid3' => array('audio' => array('bitrate_mode' => NULL)),                            'rdf' => 'wordnet:audio_bitrate_mode',       'type' => '',      'default' => 0),
    'audiolossless'         => array('name' => 'Audio lossless',           'getid3' => array('audio' => array('lossless' => NULL)),                                'rdf' => 'wordnet:audio_lossless',           'type' => '',      'default' => 0),
    'audiosamplerate'       => array('name' => 'Audio sample rate',        'getid3' => array('audio' => array('sample_rate' => NULL)),                             'rdf' => 'wordnet:audio_sample_rate',        'type' => 'int',   'default' => 1),
    'audiochannels'         => array('name' => 'Audio channels',           'getid3' => array('audio' => array('channels' => NULL)),                                'rdf' => 'wordnet:audio_channels',           'type' => 'int',   'default' => 1),
    'audiochannelmode'      => array('name' => 'Audio channel mode',       'getid3' => array('audio' => array('channelmode' => NULL)),                             'rdf' => 'wordnet:audio_channelmode',        'type' => '',      'default' => 0),
    'audioencoderoptions'   => array('name' => 'Audio encoder options',    'getid3' => array('audio' => array('encoder_options' => NULL)),                         'rdf' => 'wordnet:audio_encoder_options',    'type' => '',      'default' => 0),
    'audiocompressionratio' => array('name' => 'Audio compression ratio',  'getid3' => array('audio' => array('compression_ratio' => NULL)),                       'rdf' => 'wordnet:audio_compression_ratio',  'type' => 'float', 'default' => 1),
    'audiobitspersample'    => array('name' => 'Audio bits per sample',    'getid3' => array('audio' => array('bits_per_sample' => NULL)),                         'rdf' => 'wordnet:audio_bits_per_sample',    'type' => 'int',   'default' => 1),
    'artist'                => array('name' => 'Artist',                   'getid3' => array('id3v2' => array('comments' => array('artist' => array(0 => NULL)))), 'rdf' => 'foaf:made',       'type' => '',    'default' => 1),
    'title'                 => array('name' => 'Title',                    'getid3' => array('id3v2' => array('comments' => array('title' => array(0 => NULL)))),  'rdf' => 'dc:title',        'type' => '',    'default' => 1),
    'album'                 => array('name' => 'Album',                    'getid3' => array('id3v2' => array('comments' => array('album' => array(0 => NULL)))),  'rdf' => 'wordnet:album',   'type' => '',    'default' => 1),
    'year'                  => array('name' => 'Year',                     'getid3' => array('id3v2' => array('comments' => array('year' => array(0 => NULL)))),   'rdf' => 'wordnet:year',    'type' => 'int', 'default' => 1),
    'genre'                 => array('name' => 'Genre',                    'getid3' => array('id3v2' => array('comments' => array('genre' => array(0 => NULL)))),  'rdf' => 'wordnet:genre',   'type' => '',    'default' => 1),
    'length'                => array('name' => 'Length',                   'getid3' => array('playtime_string' => NULL),                                           'rdf' => 'wordnet:length',  'type' => '',    'default' => 1),
  );
}

