<?php

/**
 * @file
 * Audio Field module for displaying audio files as usable players.
 */

// Load all Field module hooks for Audio.
module_load_include('inc', 'audiofield', 'audio.field');
module_load_include('inc', 'audiofield', 'audiofield.players');

/**
 * Implements hook_menu().
 */
function audiofield_menu() {
  $items['admin/config/media/audiofield'] = array(
    'title' => 'Audio Field',
    'description' => 'Configure Audiofield.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('audiofield_admin_settings_form'),
    'access arguments' => array('administer site configuration'),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function audiofield_permission() {
  return array(
    'download own audio files' => array(
      'title' => t('Download Own Audio Files'),
      'description' => t('Let the users download their own audio files.'),
    ),
    'download all audio files' => array(
      'title' => t('Download All Audio Files'),
      'description' => t('Let the users download any audio files.'),
    ),
  );
}

/**
 * Accessible command line tool: ffprobe.
 */
function audiofield_accessible_ffprobe($path = '') {
  if (!$path) {
    $audiofield_detail = variable_get('audiofield_detail');
    if (isset($audiofield_detail['ffprobe_path'])) {
      $path = $audiofield_detail['ffprobe_path'];
    }
  }
  $arg = array('which', ' ', $path, 'ffprobe');
  if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
    $arg[3] = 'ffprobe.exe';
  }
  $command = implode('', $arg);
  exec($command, $output, $result);
  if ($result == 0 && count($output) == 1) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Accessible Drupal module and tool: getid3.
 */
function audiofield_accessible_getid3() {
  return module_exists('getid3') && getid3_load() && class_exists('getid3');
}

/**
 * Implements hook_validate().
 */
function audiofield_admin_settings_form_validate($form, &$form_state) {
  if (!empty($form_state['values']['audiofield_detail']['ffprobe_path'])) {
    if (!in_array(substr($form_state['values']['audiofield_detail']['ffprobe_path'], -1), array('/', '\\'))) {
      if (preg_match("@^(.*)(/|\\\\)(ffprobe|ffprobe.exe|ffmpeg|ffmpeg.exe)$@", $form_state['values']['audiofield_detail']['ffprobe_path'], $preg)) {
        $form_state['values']['audiofield_detail']['ffprobe_path'] = $preg[1];
      }
      $form_state['values']['audiofield_detail']['ffprobe_path'] .= '/';
    }
    if (!audiofield_accessible_ffprobe($form_state['values']['audiofield_detail']['ffprobe_path'])) {
      form_set_error('ffprobe_path', t('Path for ffmpeg/ffprobe is not accessible.'));
      $form_state['values']['audiofield_detail']['ffprobe_path'] = '';
    }
  }
}

/**
 * Settings form for administering module.
 */
function audiofield_admin_settings_form() {
  $audio_players = audiofield_players();
  $players = array();
  $download_players = '';
  foreach ($audio_players as $id => $player) {
    if ((isset($player['path']) && file_exists($player['path'])) || (isset($player['local']) && $player['local']) || (isset($player['module']) && module_exists($player['module']))) {
      foreach ($player['filetypes'] as $filetype) {
        $players[$filetype][$id] = $player['name'] . '<br/>'; /* . call_user_func($player['callback'], $base_path . $player['path'], "");*/
      }
    }
    else {
      if (isset($player['download_link'])) {
        $download_players .= '<li>Download ' . l($player['name'], $player['download_link']) . '</li>';
      }
    }
  }

  // MP3 Players list.
  if (!empty($players['mp3'])) {
    $form['audiofield_audioplayer'] = array(
      '#type' => 'radios',
      '#title' => t('MP3 Audio Players'),
      '#options' => $players['mp3'],
      '#default_value' => variable_get('audiofield_audioplayer', 'google_reader'),
    );
    unset($players['mp3']);
  }

  // Other players list (wav, ogg,...)
  foreach ($players as $filetype => $type_player) {
    $form['audiofield_audioplayer_' . $filetype] = array(
      '#type' => 'radios',
      '#title' => check_plain($filetype . ' ' . t('Audio Players')),
      '#options' => $type_player,
      '#default_value' => variable_get('audiofield_audioplayer_' . $filetype, 0),
    );
  }

  $form['audiofield_players_dir'] = array(
    '#type' => 'textfield',
    '#title' => t('Audio Players Directory'),
    '#description' => t('Download and extract audio players in this directory'),
    '#default_value' => variable_get('audiofield_players_dir', 'sites/all/libraries/player'),
  );

  if (!empty($download_players)) {
    $form['audiofield_downloadaudioplayer'] = array(
      '#type' => 'item',
      '#title' => t('Download and install audio players'),
      '#markup' => '<ul class="audiofield-download-players">' . $download_players . '</ul>',
    );
  }

  // File header details.
  if (!empty($players) || count($players)) {

    $detect_ffprobe = audiofield_accessible_ffprobe();
    $detect_getid3 = audiofield_accessible_getid3();

    $detail_value = variable_get('audiofield_detail');
    $form['audiofield_detail'] = array(
      '#type' => 'fieldset',
      '#title' => t('Show details'),
      'description' => array(
        '#theme' => 'item_list',
        '#type' => 'ul',
        '#prefix' => t('These details are stored on each mp3 file either as part of the file or as ID3 tags. To display these details, one of the following tools must be installed:'),
        '#items' => array(
          t('Command line tools !ffmpeg and !ffprobe. Status: !ffprobe_status', array(
            '!ffmpeg' => l(t('ffmpeg'), 'https://www.ffmpeg.org/documentation.html'),
            '!ffprobe' => l(t('ffprobe'), 'https://www.ffmpeg.org/ffprobe.html'),
            '!ffprobe_status' => $detect_ffprobe ? t('Enabled') : t('Disabled or not fully installed'),
          )),
          t('Drupal module !getid3 and getid3 command line. Status: !getid3_status', array(
            '!getid3' => l(t('getid3'), 'https://www.drupal.org/project/getid3'),
            '!getid3_status' => $detect_getid3 ? t('Enabled') : t('Disabled or not fully installed'),
          )),
        ),
        '#attributes' => array(),
      ),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
      '#tree' => TRUE,
    );

    $form['audiofield_detail']['ffprobe_path'] = array(
      '#type' => 'textfield',
      '#title' => t('Path for ffmpeg/ffprobe'),
      '#default_value' => isset($detail_value['ffprobe_path']) ? $detail_value['ffprobe_path'] : '',
      '#description' => t('In Terminal or Command Prompt execute: which ffprobe'),
    );

    $form['audiofield_detail']['filename'] = array(
      '#type' => 'select',
      '#title' => t('Filename'),
      '#options' => array(
        '0' => t('- None -'),
        '1' => t('Filename'),
        '2' => t('Filename (remove extension)'),
        '3' => t('File extension only'),
      ),
      '#default_value' => isset($detail_value['filename']) ? $detail_value['filename'] : '',
    );

    $form['audiofield_detail']['filesize'] = array(
      '#type' => 'select',
      '#title' => t('Filesize'),
      '#options' => array(
        '0' => t('- None -'),
        '1' => t('Drupal format'),
        '2' => t('Row format'),
      ),
      '#default_value' => isset($detail_value['filesize']) ? $detail_value['filesize'] : '',
    );

    // These options are only available if one of the ID3 readers is installed.
    if ($detect_getid3 || $detect_ffprobe) {
      $form['audiofield_detail']['length'] = array(
        '#type' => 'select',
        '#title' => t('Length'),
        '#options' => array(
          '0' => t('- None -'),
          '1' => t('H:m:s'),
          '2' => t('seconds'),
        ),
        '#default_value' => isset($detail_value['length']) ? $detail_value['length'] : '',
      );
      $form['audiofield_detail']['codec'] = array(
        '#type' => 'select',
        '#title' => t('Codec'),
        '#options' => array(
          '0' => t('- None -'),
          '1' => t('Short name'),
          '2' => t('Long name'),
        ),
        '#default_value' => isset($detail_value['codec']) ? $detail_value['codec'] : '',
      );
      $form['audiofield_detail']['channelmode'] = array(
        '#type' => 'select',
        '#title' => t('Channel mode'),
        '#options' => array(
          '0' => t('- None -'),
          '1' => t('Stereo / Mono'),
        ),
        '#default_value' => isset($detail_value['channelmode']) ? $detail_value['channelmode'] : '',
      );
      $form['audiofield_detail']['samplerate'] = array(
        '#type' => 'select',
        '#title' => t('Sample rate'),
        '#options' => array(
          '0' => t('- None -'),
          '1' => t('Hz'),
        ),
        '#default_value' => isset($detail_value['samplerate']) ? $detail_value['samplerate'] : '',
      );
      $form['audiofield_detail']['bitrate'] = array(
        '#type' => 'select',
        '#title' => t('Bitrate'),
        '#options' => array(
          '0' => t('- None -'),
          'b/s' => t('b/s'),
          'bit/s' => t('bit/s'),
          'kb/s' => t('kb/s'),
          'kbit/s' => t('kbit/s'),
        ),
        '#default_value' => isset($detail_value['bitrate']) ? $detail_value['bitrate'] : '',
      );
    }
    if ($detect_getid3) {
      $form['audiofield_detail']['tags_id3'] = array(
        '#type' => 'select',
        '#title' => t('ID3 tags support'),
        '#options' => array(
          '0' => t('- None -'),
          'id3' => t('ID3 row data (id3v1 & id3v2)'),
          'title-artist' => t('Title - Artist'),
          'artist-title' => t('Artist - Title'),
          'title' => t('Title'),
          'title-year' => t('Title - Year'),
          'artist-album-title' => t('Artist - Album - Title'),
          'language' => t('Language'),
          'title-language' => t('Title - Language'),
        ),
        '#default_value' => isset($detail_value['tags_id3']) ? $detail_value['tags_id3'] : '',
        '#description' => t('Limited on mp3 format'),
      );
      $form['audiofield_detail']['tags_id3_picture'] = array(
        '#type' => 'select',
        '#title' => t('Picture'),
        '#options' => array(
          '0' => t('- None -'),
          '50x50' => t('50x50 px'),
          '100x100' => t('100x100 px'),
          '150x150' => t('150x150 px'),
          '200x200' => t('200x200 px'),
          '300x300' => t('300x300 px'),
          '600x600' => t('600x600 px'),
          'original' => t('Original size'),
        ),
        '#default_value' => isset($detail_value['tags_id3_picture']) ? $detail_value['tags_id3_picture'] : '',
        '#description' => t('Limited on mp3 format'),
      );
    }
  }

  return system_settings_form($form);
}

/**
 * Implements hook_theme().
 */
function audiofield_theme() {
  $theme = _audiofield_theme();
  return $theme;
}

/**
 * FFProbe analyze.
 */
function audiofield_ffprobe_analyze($audio_path) {
  $audiofield_detail = variable_get('audiofield_detail');
  if (empty($audiofield_detail['ffprobe_path'])) {
    return FALSE;
  }

  $file_path = drupal_realpath($audio_path);
  $command = $audiofield_detail['ffprobe_path'] . 'ffprobe -v quiet -print_format json -show_format -show_streams "' . $file_path . '"';
  exec($command, $output, $result);
  if ($result == 0) {
    $array = json_decode(implode("\n", $output), TRUE);
    if (isset($array['streams'])) {
      // Find first audio stream.
      foreach ($array['streams'] as $s) {
        if (isset($s['codec_type']) && $s['codec_type'] == 'audio') {
          $array['_audio'] = $s;
          break;
        }
      }
    }
    return $array;
  }
  return FALSE;
}

/**
 * Get getid3_analyze.
 */
function audiofield_getid3_analyze($audio_path) {
  if (audiofield_accessible_getid3()) {
    if (!class_exists('getid3')) {
      drupal_set_message(t('Missing class getid3! Check getid3 module installation.'), 'error', FALSE);
      return FALSE;
    }
    else {
      $file_path = drupal_realpath($audio_path);
      $getID3 = getid3_instance();
      if (!file_exists($file_path)) {
        return array();
      }
      return $getID3->analyze($file_path);
    }
  }
  return FALSE;
}

/**
 * Get details formater.
 */
function audiofield_details_formatter($audio_path, $audiofield_detail) {
  $details = array(
    'list' => array(),
  );
  foreach ($audiofield_detail as $key => $val) {
    if ($val && !in_array($key, array('ffprobe_path', 'filename', 'filesize'))) {
      $file_info['getid3'] = audiofield_getid3_analyze($audio_path);
      $file_info['ffprobe'] = audiofield_ffprobe_analyze($audio_path);
      if (!$file_info) {
        return $details;
      }
      switch ($key) {
        case 'ffprobe_path':
          break;

        case 'filename':
          if (empty($details['list']['filename'])) {
            $details['list']['filename'] = t('Filename: @filename', array(
              '@filename' => $file_info['getid3']['filename'],
            ));
          }
          break;

        case 'filesize':
          if (empty($details['list']['filesize'])) {
            $details['list']['filesize'] = t('Filesize: $filesize', array(
              '@filesize' => ($val == '1' ? format_size($file_info['getid3']['filesize']) : $file_info['getid3']['filesize']),
            ));
          }
          break;

        case 'codec':
          $codec = '';
          $codec_long = '';
          if (!empty($file_info['ffprobe']['_audio']['codec_name'])) {
            $codec = $file_info['ffprobe']['_audio']['codec_name'];
            $codec_long = $file_info['ffprobe']['_audio']['codec_long_name'];
          }
          elseif (!empty($file_info['getid3']['audio']['codec'])) {
            $codec = $file_info['getid3']['audio']['codec'] . ' (' . (isset($file_info['getid3']['audio']['encoder_settings']) ? $file_info['getid3']['audio']['encoder_settings'] : @$file_info['getid3']['audio']['dataformat']) . ')';
            $codec_long = $codec;
          }
          $details['list'][] = t('Codec: @codec', array(
            '@codec' => ($val == '2' ? $codec_long : $codec),
          ));
          break;

        case 'length':
          $length_timeformat = '';
          $length_sec = '';
          if (!empty($file_info['ffprobe']['format']['duration'])) {
            $length_timeformat = gmdate('H:i:s', $file_info['ffprobe']['format']['duration']);
            if (substr($length_timeformat, 0, 3) == '00:') {
              $length_timeformat = substr($length_timeformat, 3);
            }
            $length_sec = $file_info['ffprobe']['format']['duration'];
          }
          elseif (!empty($file_info['getid3']['playtime_seconds'])) {
            $length_timeformat = $file_info['getid3']['playtime_string'];
            $length_sec = $file_info['getid3']['playtime_seconds'];
          }
          $details['list'][] = t('Length: @length', array(
            '@length' => ($val == '2' ? round($length_sec, 3) . ' ' . t('seconds') : $length_timeformat),
          ));
          break;

        case 'channelmode':
          $channelmode = '';
          if (!empty($file_info['ffprobe']['_audio']['channel_layout'])) {
            $channelmode = $file_info['ffprobe']['_audio']['channel_layout'];
          }
          elseif (!empty($file_info['getid3']['audio']['channelmode'])) {
            $channelmode = $file_info['getid3']['audio']['channelmode'];
          }
          $details['list'][] = t('Channel mode: @channelmode', array(
            '@channelmode' => $channelmode,
          ));
          break;

        case 'samplerate':
          $samplerate = '';
          if (!empty($file_info['ffprobe']['_audio']['sample_rate'])) {
            $samplerate = $file_info['ffprobe']['_audio']['sample_rate'];
          }
          elseif (!empty($file_info['getid3']['audio']['sample_rate'])) {
            $samplerate = $file_info['getid3']['audio']['sample_rate'];
          }
          $details['list'][] = t('Sample rate: @samplerate Hz', array(
            '@samplerate' => $samplerate,
          ));
          break;

        case 'bitrate':
          $bitrate = '';
          if (!empty($file_info['ffprobe']['_audio']['bit_rate'])) {
            $bitrate = $file_info['ffprobe']['_audio']['bit_rate'];
          }
          elseif (!empty($file_info['getid3']['bitrate'])) {
            $bitrate = $file_info['getid3']['bitrate'];
          }
          $details['list'][] = t('Bitrate: @bitrate', array(
            '@bitrate' => ((substr($val, 0, 1) == 'k') ? substr($bitrate, 0, -3) : $bitrate) . ' ' . $val,
          ));
          break;

        case 'tags_id3':
          $tags = array();
          if ($val == 'id3') {
            if (isset($file_info['getid3']['id3v2']['comments'])) {
              $id3_encoding = $file_info['getid3']['id3v2']['encoding'];
              $id3 = $file_info['getid3']['id3v2']['comments'];
            }
            elseif (isset($file_info['getid3']['id3v1']['comments'])) {
              $id3_encoding = $file_info['getid3']['id3v1']['encoding'];
              $id3 = $file_info['getid3']['id3v1']['comments'];
            }
            if (!empty($id3)) {
              foreach ($id3 as $key => $val) {
                if (is_array($val)) {
                  if (in_array($id3_encoding, array('ISO-8859-1'))) {
                    $id3[$key] = utf8_encode(implode('', $val));
                  }
                  else {
                    $id3[$key] = implode('', $val);
                  }
                }
              }
              $tags[] = '<code class="audiofield_id3">' . json_encode($id3) . '</code>';
            }
          }
          else {
            $get_tags = explode('-', $val);
            foreach ($get_tags as $tag) {
              if (isset($file_info['getid3']['id3v2']['comments'][$tag])) {
                $tags[$tag] = implode(';', $file_info['getid3']['id3v2']['comments'][$tag]);
              }
              elseif (isset($file_info['getid3']['id3v1'][$tag])) {
                $tags[$tag] = $file_info['getid3']['id3v1'][$tag];
              }
            }
          }
          $details['list']['tags'] = t('ID3 tags: !tags', array(
            '!tags' => implode(' - ', $tags),
          ));
          break;

        case 'tags_id3_picture':
          if (isset($file_info['getid3']['id3v2']['APIC'][0]['data'])) {
            if ($val != 'original') {
              $img_size = explode('x', $val);
              $details['img']['attributes'] = array('width' => $img_size[0], 'height' => $img_size[1]);
            }
            $src = 'data:' . $file_info['getid3']['id3v2']['APIC'][0]['image_mime'] . ';charset=utf-8;base64,' . base64_encode($file_info['getid3']['id3v2']['APIC'][0]['data']);
            $details['img']['attributes']['src'] = $src;
          }
          break;

        default:
          $details['list'][] = $key . ': not support';
          break;
      }
    }
  }
  return $details;
}

/**
 * Get the object for the suitable player for the parameter resource.
 */
function audiofield_get_player($audio_path, $op, $options = array()) {
  global $base_path;
  // Lets convert $op to lowercase.
  $op = strtolower($op);
  $audio_players = audiofield_players();
  $variable_name = 'audiofield_audioplayer' . ($op == 'mp3' ? '' : "_$op");
  $player_id = variable_get($variable_name, '');
  $player = isset($audio_players[$player_id]) ? $audio_players[$player_id] : NULL;

  // File header details.
  $details_html = array();
  if (empty($options['display']['type']) || in_array($options['display']['type'], array('audiofield_details'))) {
    $details = array();
    $audiofield_detail = variable_get('audiofield_detail');
    if ($audiofield_detail && is_array($audiofield_detail)) {
      // File name.
      if (!empty($audiofield_detail['filename'])) {
        $filename = drupal_basename($audio_path);

        // File name (remove extension).
        if ($audiofield_detail['filename'] == '2') {
          $last_dot = strrpos($filename, '.');
          if ($last_dot !== FALSE) {
            $filename = substr($filename, 0, $last_dot);
          }
          $details['list']['filename'] = t('Filename: @filename', array(
            '@extension' => $filename,
          ));
        }
        // Only extension.
        elseif ($audiofield_detail['filename'] == '3') {
          $last_dot = strrpos($filename, '.');
          if ($last_dot === FALSE) {
            $ext = '';
          }
          else {
            $ext = substr($filename, $last_dot);
          }
          $details['list']['filename'] = t('Extension: @extension', array(
            '@extension' => $ext,
          ));
        }
        // Def. file name.
        else {
          $details['list']['filename'] = t('Filename: @filename', array(
            '@filename' => $filename,
          ));
        }
      }
      // File size.
      if (!empty($audiofield_detail['filesize'])) {
        $filesize = filesize(drupal_realpath($audio_path));
        if ($audiofield_detail['filesize'] == '1') {
          $filesize = format_size($filesize);
        }
        $details['list']['filesize'] = t('Filesize: @filesize', array(
          '@filesize' => $filesize,
        ));
      }
      // GetID3/ffprobe details.
      $audio_details = audiofield_details_formatter($audio_path, $audiofield_detail);
      $audio_details['list'] = array_merge($details['list'], $audio_details['list']);
    }

    if (!empty($audio_details)) {
      $details_img = array();
      if (isset($audio_details['img']['attributes']['src'])) {
        $details_img = array(
          '#type' => 'container',
          '#attributes' => array(
            'class' => array(
              'audiofield_img',
            ),
          ),
          array(
            '#theme' => 'html_tag',
            '#tag' => 'img',
            '#attributes' => $audio_details['img']['attributes'],
          ),
        );
      }
      $details_html = array(
        $details_img,
        array(
          '#theme' => 'item_list',
          '#items' => $audio_details['list'],
          '#title' => NULL,
          '#type' => 'ul',
          '#attributes' => array(
            'class' => array(
              'tips',
              'audiofield_detail',
            ),
          ),
        ),
      );
    }
  }

  if (empty($player)) {
    return array(
      audiofield_html5_audio('', $audio_path),
      $details_html,
    );
  }
  if (isset($player['path'])) {
    $path = $base_path . $player['path'];
  }
  else {
    $path = '';
  }
  return array(
    call_user_func($player['callback'], $path, $audio_path, $options),
    $details_html,
  );
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function audiofield_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
  $instance = $form['#instance'];

  if ($instance['widget']['type'] == 'audiofield_widget' && $form['instance']['settings']['file_extensions']['#default_value'] == 'txt') {
    $form['instance']['settings']['file_extensions']['#default_value'] = 'mp3';
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Modify the add new field form to change the default formatter.
 */
function audiofield_form_field_ui_field_settings_form_alter(&$form, &$form_state) {
  $form['#submit'][] = 'audiofield_form_content_field_overview_submit';
}

/**
 * Submit handler to set a new field's formatter to "audiofield_embedded".
 */
function audiofield_form_content_field_overview_submit(&$form, &$form_state) {
  $entity_type = 'node';
  $field_name = $form_state['values']['field']['field_name'];
  $bundle = $form_state['complete form']['#bundle'];
  $instance = field_read_instance($entity_type, $field_name, $bundle);

  if ($instance['widget']['module'] == 'audiofield') {
    foreach ($instance['display'] as $display_type => $display_settings) {
      if ($instance['display'][$display_type]['type'] == 'file_default') {
        $instance['display'][$display_type]['type'] = 'audiofield_embedded';
      }
    }
    field_update_instance($instance);
  }
}

/**
 * Implements hook_field_conditional_state_settings_alter().
 *
 * Add support for Field Conditional States.
 */
function audiofield_field_conditional_state_settings_alter(&$settings) {
  $settings['audiofield_widget'] = array(
    'form_elements' => array(0 => array(0, 'upload')),
    'field_data' => array(0),
    'reprocess_from_root' => TRUE,
    'field_states' => array(
      'enabled',
      'disabled',
      'required',
      'optional',
      'visible',
      'invisible',
    ),
    'trigger_states' => array('empty', 'filled'),
    'trigger_value_widget' => '_field_conditional_state_default_trigger_value_widget',
    'trigger_value_submit' => '_field_conditional_state_default_trigger_value_submit',
  );
}

/**
 * Implements hook_filefield_sources_widgets().
 *
 * This returns a list of widgets that are compatible with FileField Sources.
 */
function audiofield_filefield_sources_widgets() {
  return array('audiofield_widget');
}
