1
<?php
2
3
4
// This implements a wrapper for FFmpeg so that we don't have to reinvent the
5
// the wheel every time we want to do something with video, audio, or images
6
7
8
/* ************************************************ */
9
/* DRUPAL HOOKS */
10
/* ************************************************ */
11
12
/**
13
 * Implementation of hook_menu().
14
 */
15
function ffmpeg_wrapper_menu() {
16
  $items = array();
17
  $items['admin/settings/ffmpeg_wrapper'] = array(
18
    'title' => 'FFmpeg Wrapper',
19
    'page callback' => 'drupal_get_form',
20
    'page arguments' => array('ffmpeg_wrapper_admin'),
21
    'access arguments' => array('administer ffmpeg wrapper'),
22
  );
23
  $items[] = array(
24
    'path' => 'admin/settings/ffmpeg_wrapper/default',
25
    'title' => 'FFmpeg Wrapper',
26
    'type' => MENU_DEFAULT_LOCAL_TASK,
27
  );
28
29
  // hand back the specific configurations for a codec
30
  $items['ffmpeg_wrapper/output'] = array(
31
    'title' => 'FFmpeg Wrapper',
32
    'page callback' => 'ffmpeg_wrapper_output_display',
33
    'page arguments' => array(2),
34
    'access arguments' => array('access content'),
35
    'type' => MENU_CALLBACK,
36
  );
37
38
  // testing utilities
39
  $items['admin/settings/ffmpeg_wrapper/test'] = array(
40
    'title' => t('FFmpeg Wrapper Test Conversion'),
41
    'page callback' => 'drupal_get_form',
42
    'page arguments' => array('ffmpeg_wrapper_ffmpeg_test_form'),
43
    'type' => MENU_LOCAL_TASK,
44
    'access arguments' => array('administer ffmpeg wrapper'),
45
    'file' => 'ffmpeg_wrapper_test_convert.inc',
46
  );
47
  $items['admin/settings/ffmpeg_wrapper/test_cleanup'] = array(
48
    'title' => 'FFmpeg Wrapper Test Cleanup',
49
    'page callback' => 'drupal_get_form',
50
    'page arguments' => array('ffmpeg_wrapper_test_cleanup_form'),
51
    'type' => MENU_LOCAL_TASK,
52
    'access arguments' => array('administer ffmpeg wrapper'),
53
    'file' => 'ffmpeg_wrapper_test_convert.inc',
54
  );
55
56
  // hand back the specific configurations for a codec
57
  $items['ffmpeg_wrapper/file_data'] = array(
58
    'title' => 'FFmpeg Wrapper',
59
    'page callback' => 'ffmpeg_wrapper_file_type_ahah',
60
    'access arguments' => array('administer ffmpeg wrapper'),
61
    'file' => 'ffmpeg_wrapper_test_convert.inc',
62
    'type' => MENU_CALLBACK,
63
  );
64
  return $items;
65
}
66
67
68
/**
69
 * Implementation of hook_perm().
70
 */
71
function ffmpeg_wrapper_perm() {
72
  return array('administer ffmpeg wrapper');
73
}
74
75
76
/**
77
 * add a link to do conversion testing on the standard file display
78
 * checks to make sure the file is usable by ffmpeg
79
 *
80
 * @param string $form_id
81
 * @param array $form
82
 */
83
function ffmpeg_wrapper_form_alter(&$form, $form_state, $form_id) {
84
  // if user does not have rights to test with ffmpeg
85
  if (! user_access('administer ffmpeg wrapper')) {
86
    return;
87
  }
88
89
  // Are there attached files?
90
  if (isset($form['attachments']) && $files = $form['attachments']['wrapper']['files']) {
91
    foreach ($files as $fid => $file) {
92
      // check to make sure this is a file array and if this file is decodeable by ffmpeg
93
      // we are dealing with a form element, so we have to make sure that this is the part that we want
94
      if (is_array($file)) {
95
        // can ffmpeg decode this?
96
        if (ffmpeg_wrapper_can_decode($file['filepath']['#value'])) {
97
          $arguments = array();
98
          // are we on a node page? Add a $nid to the arguments
99
          if ($nid = $form['#node']->nid) { $arguments[] = "nid=$nid"; }
100
          // we need a path to the file to the arguments
101
          $arguments[] = 'path='. $file['filepath']['#value'];
102
           // create the link for test conversion
103
          $link = '<br />'. l(t('Test convert file with FFmpeg'), 'admin/settings/ffmpeg_wrapper/test', array('query' => implode('&', $arguments)));
104
          // now we alter the description of this item
105
          $form['attachments']['wrapper']['files'][$fid]['description']['#description'] .= $link;
106
        }
107
      }
108
    }
109
  }
110
}
111
112
113
/**
114
 * Implementation of Drupal 6's theme registry
115
 * @return array
116
 */
117
function ffmpeg_wrapper_theme() {
118
  return array(
119
    'ffmpeg_wrapper_files_checkboxes' => array(
120
      'arguments' => array('form' => NULL),
121
    ),
122
    'ffmpeg_wrapper_files_radios' => array(
123
      'arguments' => array('form' => NULL),
124
    ),
125
  );
126
}
127
128
129
/* ************************************************ */
130
/* FFmpeg Wrapper Admin Functions                   */
131
/* ************************************************ */
132
133
/**
134
 * Build the admin form.
135
 */
136
function ffmpeg_wrapper_admin() {
137
  // always clear caches when if settings are updated
138
  cache_clear_all('ffmpeg_wrapper_codecs', 'cache');
139
  cache_clear_all('ffmpeg_wrapper_output_formats', 'cache');
140
  cache_clear_all('ffmpeg_wrapper_file_formats', 'cache');
141
142
  $form['ffmpeg_wrapper'] = array(
143
    '#type' => 'fieldset',
144
    '#title' => t('FFmpeg'),
145
  );
146
147
  $form['ffmpeg_wrapper']['ffmpeg_wrapper_path'] = array(
148
    '#type' => 'textfield',
149
    '#title' => t('FFmpeg path'),
150
    '#default_value' => variable_get('ffmpeg_wrapper_path', '/usr/bin/ffmpeg'),
151
    '#description' => t('Absolute path to the FFmpeg exeutable. Leave blank if you do not need this.'),
152
  );
153
154
  $form['ffmpeg_wrapper']['ffmpeg_wrapper_vhook'] = array(
155
    '#type' => 'textfield',
156
    '#title' => t('Path to the FFmpeg vhook libraries'),
157
    '#default_value' => variable_get('ffmpeg_wrapper_vhook', '/usr/local/lib/vhook'),
158
    '#description' => t('Absolute path to the FFmpeg vhook directory. No trailing slash. Leave blank if you do not need this'),
159
  );
160
161
  // configuration options
162
  // only display if we can reach the binary
163
  if (ffmpeg_wrapper_run_command()) {
164
    $form['ffmpeg_wrapper']['ffmpeg_wrapper_about'] = array(
165
      '#type' => 'fieldset',
166
      '#title' => t('About FFmpeg installation'),
167
      '#collapsible' => true,
168
      '#collapsed' => true,
169
    );
170
171
    $form['ffmpeg_wrapper']['ffmpeg_wrapper_about']['ffmpeg_wrapper_version'] = array(
172
      '#type' => 'item',
173
      '#title' => t('FFmpeg version'),
174
      '#value' =>  '<blockquote>'. ffmpeg_wrapper_get_version('string') .'</blockquote>',
175
      '#description' => t('Version of FFmpeg running on your system'),
176
    );
177
178
    $form['ffmpeg_wrapper']['ffmpeg_wrapper_about']['ffmpeg_wrapper_formats'] = array(
179
      '#type' => 'item',
180
      '#title' => t('Supported file formats'),
181
      '#value' =>  ffmpeg_wrapper_formats_data_display(),
182
      '#description' => t('File formats that the installed version of FFmpeg supports.'),
183
    );
184
185
    $form['ffmpeg_wrapper']['ffmpeg_wrapper_about']['ffmpeg_wrapper_codecs'] = array(
186
      '#type' => 'item',
187
      '#title' => t('Installed codecs'),
188
      '#value' => ffmpeg_wrapper_get_codecs_display(),
189
      '#description' => t('FFmpeg was either compiled with these codecs, or these are the codecs available on your system'),
190
    );
191
  }
192
193
  // get a list of the vhooks in the system
194
  if (ffmpeg_wrapper_vhook_list()) {
195
    $form['ffmpeg_wrapper']['ffmpeg_wrapper_vhooks'] = array(
196
      '#type' => 'fieldset',
197
      '#title' => t('vhook files installed on this system'),
198
      '#collapsible' => true,
199
      '#collapsed' => true,
200
    );
201
    $form['ffmpeg_wrapper']['ffmpeg_wrapper_vhooks']['ffmpeg_wrapper_vhook'] = array(
202
      '#type' => 'item',
203
      '#title' => t('vhook files'),
204
      '#value' =>  implode('<br />', ffmpeg_wrapper_vhook_list()),
205
      '#description' => t('List of all the Vhook files found.'),
206
    );
207
  }
208
  return system_settings_form($form);
209
}
210
211
212
/**
213
 * validate the options on the ffmpeg form
214
 *
215
 * @param int $form_id
216
 * @param array $form_values
217
 */
218
function ffmpeg_wrapper_admin_validate($form, &$form_state) {
219
  // make sure we've got the path to the ffmpeg binary
220
  if (! ffmpeg_wrapper_run_command(null, false, $form_state['values']['ffmpeg_wrapper_path']) && $form_values['ffmpeg_wrapper_path']) {
221
    form_set_error('ffmpeg_wrapper_path', t('FFmpeg binary was not found on the path you specified. Maybe try a different path?'));
222
  }
223
224
  // check and see if we can find the vhook directory
225
  if (! is_dir($form_state['values']['ffmpeg_wrapper_vhook']) && $form_state['values']['ffmpeg_wrapper_vhook']) {
226
    form_set_error('ffmpeg_wrapper_vhook', t('The vhook directory was not found on the path you specified. Maybe try a different path?'));
227
  }
228
}
229
230
231
/* ************************************************ */
232
/* Interactions with ffmpeg                         */
233
/* ************************************************ */
234
235
/**
236
 * Get data from ffmpeg.
237
 *
238
 * @param $command
239
 *   The options to run ffmpeg with.
240
 * @param $error_check
241
 *   If TRUE, runs error checking on the output.
242
 * @param $path
243
 *   Overrides the system settings.
244
 * @param $ffmpeg_object
245
 *   Object for passing debug and other kinds of data through the system.
246
 * @return
247
 *   Output of the command.
248
 */
249
function ffmpeg_wrapper_run_command($command = '', $error_check = true, $path = '', &$ffmpeg_object = null) {
250
  // override the system path?
251
  if (! $path) {
252
    $path = variable_get('ffmpeg_wrapper_path', '/usr/bin/ffmpeg');
253
  }
254
  if (empty($ffmpeg_object)) {
255
    $ffmpeg_object = new stdClass();
256
  }
257
258
  // Create some exceptions for dealing with pipes and semicolons
259
  $pattern = array('\;', '\|');
260
  $replace = array(';', '|');
261
262
  // Escape our command, but make sure that we allow | and ;
263
  $ffmpeg_object->command = str_replace($pattern, $replace, escapeshellcmd($path .' '. $command));
264
265
  $ffmpeg_object->cwd = getcwd();
266
267
  // does binary exist?
268
  if (! file_exists($path)) {
269
    return false;
270
  }
271
272
  $descriptor_spec = array(
273
     0 => array('pipe', 'r'),
274
     1 => array('pipe', 'w'),
275
     2 => array('pipe', 'w')
276
  );
277
278
  $pipes = array();
279
  $process = proc_open($ffmpeg_object->command, $descriptor_spec, $pipes, $ffmpeg_object->cwd , null, array('binary_pipes' => true));
280
  if (is_resource($process)) {
281
    fclose($pipes[0]);
282
    $ffmpeg_object->output = stream_get_contents($pipes[1]);
283
    fclose($pipes[1]);
284
    $ffmpeg_object->output .= stream_get_contents($pipes[2]);
285
    fclose($pipes[2]);
286
    $command_return = proc_close($process);
287
  }
288
289
290
  // Find the output file in the command if there was one.
291
  // This is sort of hacky but is helpful for passing the file out.
292
  preg_match("/.*'(.*)'$/i", $command, $matches);
293
  $ffmpeg_object->output_file = !empty($matches[1]) ? $matches[1] : null;
294
295
  // do error handling if requested
296
  if ($error_check) {
297
    if (! ffmpeg_wrapper_error_check($ffmpeg_object, true)){
298
      return false;
299
    }
300
  }
301
  return $ffmpeg_object->output;
302
}
303
304
305
/**
306
 * builds a list of the ffmpeg vhook options installed on this machine
307
 * @TODO we need to get more information on how the vhook system works and how
308
 *       admins can utilize it
309
 *
310
 * @param string $path
311
 * @return array
312
 */
313
function ffmpeg_wrapper_vhook_list($path = '') {
314
  static $files;
315
316
  // if we have a list already
317
  if ($files) {
318
     return $files;
319
  }
320
321
  // build the path
322
  if (! $path) {
323
    $path = variable_get('ffmpeg_wrapper_vhook', '/usr/local/lib/vhook');
324
  }
325
326
  // @TODO replace below with file_scan_directory($path);
327
328
  // check to see if the directory is correct
329
  if (is_dir($path)) {
330
    // open the directory
331
    if ($dir = opendir($path)) {
332
      while (($file = readdir($dir)) !== false) {
333
        // do not this or parrent directory
334
        if ($file != "." && $file != "..") {
335
          $files[] = $path .'/'. $file;
336
        }
337
      }
338
      closedir($dir);
339
    }
340
    if (count($files)) {
341
      return $files;
342
    }
343
  }
344
}
345
346
347
/**
348
 * Check an incoming file to see if it can be decoded by comparing the file's codec
349
 * and format against the list of decode formats in FFMPEG.
350
 *
351
 * @param $file
352
 *   string, A full system filepath.
353
 * @param $types
354
 *   array, what kind of decode do we need to check for
355
 * @return
356
 *   boolean, TRUE if file is in the list of decodeable files.
357
 */
358
function ffmpeg_wrapper_can_decode($path, $types = array('video', 'audio')) {
359
  // get the kind of files
360
  $file_types = ffmpeg_wrapper_get_file_formats('decode');
361
362
  // get the format and codec information on this file
363
  $file_data = ffmpeg_wrapper_file_data($path);
364
365
  // Issue http://drupal.org/node/479972
366
  $codec = ffmpeg_wrapper_get_codecs('decode');
367
  foreach (array('video', 'audio') as $media) {
368
    // some files have multiple formats attached to them
369
    foreach(explode(',', $file_data[$media]['codec']) as $type) {
370
      // is this type in the array of items that we can decode?
371
      if (in_array($type, $codec)) {
372
        return true;
373
      }
374
    }
375
  }
376
377
  // if the file has multiple formats, all formats
378
  // must be in $file_types Otherwise, it is
379
  // not supported by ffmpeg.
380
  foreach(explode(',', $file_data['format']) as $type) {
381
    if (! in_array($type, $file_types)) {
382
      return false;
383
    }
384
  }
385
386
  // if we have na values for the audio and video codecs, the file can not be decoded
387
  // @ TODO is this really true? The logic below does not seem to indicate this,
388
  //        however, I might not understand what the 'na' value really means
389
  if ($file_data['audio']['codec'] == 'na' && $file_data['video']['codec'] == 'na') {
390
    return false;
391
  }
392
393
  // Now get installed codecs to compare file codecs
394
  foreach (ffmpeg_wrapper_get_codecs('decode') as $codec) {
395
    if ($codec == $file_data['audio']['codec'] || $file_data['audio']['codec'] == 'na' ) {
396
       // are we only handing back audio?
397
      if ($types['audio'] && ! $types['video']) { return true; }
398
      $can_audio_decode = true;
399
    }
400
401
    if ($codec == $file_data['video']['codec'] || $file_data['video']['codec'] == 'na') {
402
      // are we only handing back video?
403
      if ($types['video'] && ! $types['audio']) { return true; }
404
      $can_video_decode = true;
405
    }
406
407
    // If we can decode both audio and video, the file is ok
408
    if ($can_audio_decode && $can_video_decode) {
409
      return true;
410
    }
411
  }
412
  return false;
413
}
414
415
416
/**
417
 * Get an array of codec types usable on this system.
418
 * This should probably be smoothed out so that it doesn't rely on text so
419
 * much.
420
 * Caches data to avoid extra command line hits.
421
 *
422
 * @ TODO this needs to be rethought to pass params right
423
 *
424
 * @param $return
425
 *   Determins hand back of encode/decode.
426
 * @return
427
 *   Array of codecs or specific encode/decode options.
428
 */
429
function ffmpeg_wrapper_get_codecs($return = 'rows') {
430
  $cache_id = 'ffmpeg_wrapper_codecs';
431
  $cache = cache_get($cache_id, 'cache');
432
  if (!isset($cache->data)) {
433
    $data = array();
434
    // we know where the codecs are by looking at the output of ffmpeg -formats or ffmpeg -codecs
435
    // depending of version SVN-r > 20561 or version > 0.5
436
    $ffmpeg_version = ffmpeg_wrapper_get_version('svn');
437
    if ( ((int)$ffmpeg_version >= 20561) || ((int)$ffmpeg_version < 1000 && (float)$ffmpeg_version > 0.5) ) {
438
      $output = ffmpeg_wrapper_run_command('-codecs');
439
      $codecs_formats_pos = strpos($output, 'Codecs:');
440
      $codecs_formats_pos_end = strpos($output, 'Note,');
441
    }
442
    else {
443
      $output = ffmpeg_wrapper_run_command('-formats');
444
      $codecs_formats_pos = strpos($output, 'Codecs:');
445
      $codecs_formats_pos_end = strpos($output, 'Supported file protocols:');
446
    }
447
    $codecs = substr($output, $codecs_formats_pos, ($codecs_formats_pos_end - $codecs_formats_pos));
448
    // remove the extra text
449
    $codecs = str_replace('Codecs:', '', $codecs);
450
    // convert to array
451
    $codecs = explode("\n", $codecs);
452
453
    $rows = array();
454
    foreach ($codecs as $codec) {
455
      // match the decode, encode, type, S|D|T options (see: http://lists.mplayerhq.hu/pipermail/ffmpeg-user/2006-January/002003.html)
456
      // name
457
      $pattern ='/[ ]*([D ])([E ])([ VA])([S ])([ D])([ T])[ ]*([a-zA-Z0-9_,]*)[ ]*([a-zA-Z0-9,_ ]*)/';
458
      preg_match($pattern, $codec, $matches);
459
460
      // codec names
461
      $a_format['name'] = $matches[7];
462
463
      // get the codec type
464
      if ($matches[3] == 'A') {
465
        $a_format['type'] = t('audio');
466
        $encode_formats[] = $a_format['name'];
467
      }
468
      else {
469
        $a_format['type'] = t('video');
470
      }
471
472
      // get the decode value
473
      if ($matches[1] == 'D') {
474
        $a_format['decode'] = t('yes');
475
        $decode_formats[] = $a_format['name'];
476
      }
477
      else {
478
        $a_format['decode'] = t('no');
479
      }
480
481
      // get the encode value
482
      if ($matches[2] == 'E') {
483
        $a_format['encode'] = t('yes');
484
        $encode_formats[] = $a_format['name'];
485
      }
486
      else {
487
        $a_format['encode'] = t('no');
488
      }
489
490
      if ($a_format['name']) {
491
        $rows[] = $a_format;
492
      }
493
      $a_format = null;
494
    }
495
    $data['encode'] = $encode_formats;
496
    $data['decode'] = $decode_formats;
497
    $data['rows'] = $rows;
498
    cache_set($cache_id, $data, 'cache', CACHE_TEMPORARY);
499
  }
500
  else {
501
    $data = $cache->data;
502
  }
503
504
  return $data[$return];
505
}
506
507
508
/**
509
 * Get a list of codecs in key value form- for use in form display
510
 *
511
 * @param $type
512
 *   audio or video.
513
 * @return
514
 *   Array of codec names.
515
 */
516
function ffmpeg_wrapper_return_codecs($type) {
517
  static $codecs;
518
  if (! empty($codecs[$type])) {
519
    return $codecs[$type];
520
  }
521
  $codecs = array();
522
  $codecs[$type] = array(0 => t('Use default'));
523
  // get list of avaiable audio and video codecs
524
  $codec_list = ffmpeg_wrapper_get_codecs();
525
  if ($codec_list) {
526
    foreach ($codec_list as $codec) {
527
      if ($codec['encode'] == "yes" && $codec['type'] == $type) {
528
        $codecs[$type][$codec['name']] = $codec['name'];
529
      }
530
    }
531
  }
532
  return $codecs[$type];
533
}
534
535
536
/**
537
 * Helper function to build the list of output formats on the system.
538
 * Data is cached to reduce hits to ffmpeg.
539
 *
540
 * @return
541
 *   Array of key values
542
 */
543
function ffmpeg_wrapper_output_formats() {
544
  $cache_id = 'ffmpeg_wrapper_output_formats';
545
  $cache = cache_get($cache_id, 'cache');
546
  if (!isset($cache->data)) {
547
    // get all the encoding options
548
    if (! $outputs = ffmpeg_wrapper_get_file_formats('encode')) {
549
    	watchdog('ffmpeg_wrapper', 'No output formats found. Path to ffmpeg is probably wrong', array(), WATCHDOG_ERROR);
550
    	return array();
551
    }
552
    // rebuild as a select array
553
    $formats = array(t('Select output type'));
554
    foreach ($outputs as $output) {
555
      $formats[$output] = $output;
556
    }
557
    cache_set($cache_id, $formats, 'cache', CACHE_TEMPORARY);
558
  }
559
  else {
560
    $formats = $cache->data;
561
  }
562
  return $formats;
563
}
564
565
566
/**
567
 * Build the output rates for each type of bit rate that ffmpeg offers.
568
 *
569
 * @param $type
570
 *   Type of bit rate: "ab", "ar", "fps" or "br".
571
 * @return
572
 *   Array of key values.
573
 */
574
function ffmpeg_wrapper_output_rates($type) {
575
  static $rates;
576
  if (! $rates) {
577
    $rates = array(
578
     'ab'  => array('16k' => '16k', '22k' => '22k', '32k' => '32k', '64k' => t('64k (default)'), '128k' => '128k', '192k' => '192k', '256k' => '256k'),
579
     'ar' => array('11025' => t('11khz'), '22050' => t('22khz'), '32000' => t('32khz'), '44100' => t('44.1khz (default)') ),
580
     'fps' => array(10 => 10, 15 => 15, 20 => 20, 25 => t('25 (default)'), 29.97 => 29.97),
581
     'br' => array('50k' => t('50kps'), '100k' => t('100kps'), '150k' => t('150kps'), '200k' => t('200kps'), '250k' => t('250kps (default)'), '300k' => t('300kps'), '500k' => t('500kps'), '750k' => t('750kps'), '1000k' => t('1000kps'), '1250k' => t('1250kps'), '1500k' => t('1500kps'), '2000k' => t('2000kps')),
582
    );
583
  }
584
  return $rates[$type];
585
}
586
587
588
/**
589
* Output dimentions form settings.
590
* @return
591
*   An array of frame sizes.
592
*/
593
function ffmpeg_wrapper_frame_sizes(){
594
  $frame_sizes = array(
595
    '0' => t('No alteration'),
596
    '128x96' => '128x96',
597
    '176x144' => '176x144',
598
    '320x240' => '320x240',
599
    '352x288' => '352x288',
600
    '512x386' => '512x386',
601
    '704x576' => '704x576'
602
  );
603
  $frame_sizes['other'] = t('Other');
604
  return $frame_sizes;
605
}
606
607
608
/**
609
 * Get an array of format types usable on this system.
610
 * This should probably be smoothed out so that it doesn't rely on text so
611
 * much. If no value for $ret is given, return the descriptions. This data
612
 * is all built by scanning the output from ffmpeg.
613
 *
614
 * @param $ret
615
 *   Determins what to hand back (encode/decode).
616
 * @return
617
 *   Array of options.
618
 */
619
function ffmpeg_wrapper_get_file_formats($ret = 'row') {
620
  $cache_id = 'ffmpeg_wrapper_file_formats';
621
  $cache = cache_get($cache_id, 'cache');
622
  // do we have cached data?
623
  if (!isset($cache->data)) {
624
    // if we can't get formats, do not bother
625
    if (! $formats = ffmpeg_wrapper_run_command('-formats') ) {
626
      return;
627
    }
628
    // slice up the format output
629
    $startpos = strpos($formats, 'File formats:');
630
    // depending of version SVN-r > 20561 or version > 0.5 string changes
631
    $ffmpeg_version = ffmpeg_wrapper_get_version('svn');
632
    if ( ((int)$ffmpeg_version >= 20561) || ((int)$ffmpeg_version < 1000 && (float)$ffmpeg_version > 0.5)  ) {
633
      $endpos = strpos($formats, 'FFmpeg version');
634
    }
635
    else {
636
      $endpos = strpos($formats, 'Codecs:');
637
    }
638
    $formats = substr($formats, $startpos, $endpos - $startpos);
639
    //remove the header
640
    $formats = str_replace('File formats:', '', $formats);
641
642
    $decode_formats = array();
643
    $encode_formats = array();
644
645
    $rows = array();
646
647
    foreach (explode("\n", $formats) as $format) {
648
      // match the decode, encode, format, description
649
      $pattern ='/[ ]*([D ])([E ])[ ]*([a-zA-Z0-9_,]*)[ ]*([^\$]*)/';
650
      preg_match($pattern, $format, $matches);
651
652
      $a_format['type'] = $matches[3];
653
      $a_format['name'] = $matches[4];
654
655
      // check for decoding
656
      if ($matches[1] == 'D') {
657
        $a_format['decode'] = t('yes');
658
        // we can have multiple types per format
659
        $types = explode(',', $a_format['type']);
660
        foreach ($types as $type) {
661
          $decode_formats[] = $type;
662
        }
663
      }
664
      else {
665
        $a_format['decode'] = t('no');
666
      }
667
668
      // check for encoding
669
      if ($matches[2] == 'E') {
670
        $a_format['encode'] = t('yes');
671
        // we can have multiple types per format
672
        $types = explode(',', $a_format['type']);
673
        foreach ($types as $type) {
674
          $encode_formats[] = $type;
675
        }
676
      }
677
      else {
678
        $a_format['encode'] = t('no');
679
      }
680
681
      $a_format['description'] = $matches[4];
682
      if ($a_format['description']) {
683
        $rows[] = $a_format;
684
      }
685
    }
686
687
    $output = array();
688
    $output['encode'] = $encode_formats;
689
    $output['decode'] = $decode_formats;
690
    $output['row'] = $rows;
691
692
    cache_set($cache_id, $output, 'cache', CACHE_TEMPORARY);
693
  }
694
  else {
695
    $output = $cache->data;
696
  }
697
  // return the requested data
698
  return $output[$ret];
699
}
700
701
/**
702
 * Get FFMpeg version
703
 *
704
 * @param $format
705
 *   (string/version/svn)
706
 * @return
707
 *   Version string.
708
 */
709
function ffmpeg_wrapper_get_version($format) {
710
  // get string from ffmpeg and return it if it is enough
711
  $version = ffmpeg_wrapper_run_command('-version');
712
  if ($format == 'string') {
713
    return $version;
714
  }
715
  //anything else return the version string or SVN-r
716
  // slice up the version output
717
  $startpos = strpos($version, 'FFmpeg version ') +15;
718
  $endpos = strpos($version, ', Copyright');
719
  $version_number = substr($version, $startpos, $endpos - $startpos);
720
  if ($format == 'version') {
721
    return $version_number;
722
  }
723
  // Return SVN revision number format used by Debian lenny.
724
  if (preg_match('/^r([0-9]+)\+/', $version_number, $matches)) {
725
    return $matches[1];
726
  }
727
  // is FALSE if the string is not found
728
  if (strpos($version_number, 'SVN-r') === 0) {
729
    $version_release = substr($version_number, 5);
730
    return $version_release;
731
  }
732
  // then is a full release version as 0.5
733
  return $version_number;
734
}
735
736
/**
737
 * Get the duration of a video.
738
 *
739
 * @param $path
740
 *   The path to file.
741
 * @param $timecode
742
 *   If TRUE, return time code, otherwise return seconds.
743
 * @param $output
744
 *   string, output of ffmpeg if it has already been run
745
 * @return
746
 *   Duration in seconds as an integer or timecode as string.
747
 */
748
function ffmpeg_wrapper_file_duration($path, $timecode = null, $output = null) {
749
  // do we have any output from ffmpeg already?
750
  if (! $output) {
751
    // get duration from ffmpeg
752
    // need quotes around the path parameter in case filename has spaces.
753
    $output = ffmpeg_wrapper_run_command("-i \"$path\"");
754
  }
755
756
  // parse the output looking for "Duration: 00:02:12"
757
  $pattern = "/Duration: ([0-9]+:[0-9]+:[0-9]+)\.[0-9]+/";
758
  preg_match($pattern, $output, $matches);
759
760
  $time = $matches[1];
761
762
  if (! $timecode) {
763
    // now we need to convert the time code to seconds
764
    // get the time into an array
765
    $time = explode(':', $time);
766
767
    $seconds = 0;
768
    if ($time[0] != '00') {
769
      $seconds += $time[0] * 60 * 60;
770
    }
771
    if ($time[1] != '00') {
772
      $seconds += $time[1] * 60;
773
    }
774
    $seconds += $time[2];
775
    $time = $seconds;
776
  }
777
  return $time;
778
}
779
780
781
/**
782
 * This function produces file data from an incoming file
783
 * @param $path
784
 * @param $timecode
785
 * @return array
786
 */
787
function ffmpeg_wrapper_file_data($path = null) {
788
  if (file_exists($path)) {
789
    // get duration from ffmpeg
790
    // need quotes around the path parameter in case filename has spaces.
791
    $output = ffmpeg_wrapper_run_command("-i \"$path\"");
792
793
    // get file format
794
    $pattern = '/Input #0, (.*),/';
795
    preg_match($pattern, $output, $matches);
796
    $file['format'] = !empty($matches[1]) ? $matches[1] : 'na';
797
798
    // get file duration
799
    $file['duration'] = ffmpeg_wrapper_file_duration(null, null, $output);
800
801
    // get bit rate
802
    $pattern = "/bitrate: ([0-9].*\/s)/";
803
    preg_match($pattern, $output, $matches);
804
    $file['bitrate'] = !empty($matches[1]) ? $matches[1] : 'na';
805
806
    // get audio settings
807
    // format is: codec, sample rate, stereo/mono, bitrate
808
    $pattern = "/Audio: (.*), ([0-9]*) Hz, (stereo|mono|([0-9]+) channels)/";
809
    preg_match($pattern, $output, $matches);
810
    $file['audio']['codec'] = !empty($matches[1]) ? $matches[1] : 'na';
811
    $file['audio']['ar'] = !empty($matches[2]) ? $matches[2] : 'na';
812
    $file['audio']['ac'] = !empty($matches[4]) ? $matches[4] : (!empty($matches[3]) && $matches[3] == 'stereo' ? 2 : 1);
813
814
    // take the last match and extract the bit rate if present
815
    $pattern = "/Audio: .* (.*) kb\/s/";
816
    preg_match($pattern, $output, $matches);
817
    $file['audio']['ab'] = !empty($matches[1]) ? $matches[1] : 'na';
818
819
    // VIDEO ----------------------------------------
820
    // The formating of video can be difficult. We use 3 different
821
    // patterns to look for the video information
822
823
    // try pattern that takes into account a codec's color space (example: yuv420p)
824
    // eg: Video: mpeg1video, yuv420p, 320x240 [PAR 1:1 DAR 4:3], 990 kb/s, 30.00 tb(r)
825
    // the above is: codec, color space, frame size, bitrate, frame rate
826
    $pattern1 = "/Video: ([^,]+), ([^,]+), ([0-9x]+)[^,]*, ([0-9]*.*\/s|[A-Za-z]+[^,]*), ([0-9\.]*)/";
827
828
    // pattern that omits video bitrate but not color space.
829
    // eg: Video: mpeg4, yuv420p, 640x480 [PAR 1:1 DAR 4:3], 23.98 tb(r)
830
    // the above is: codec, color space, frame size, frame rate
831
    $pattern2 = "/Video: ([^,]+), ([^0-9][^,]*), ([0-9x]+)[^,]*, ([0-9\.]*)/";
832
833
    // pattern that omits a codec's color space and video bitrate
834
    // eg: Video: mpeg4, 640x480, 29.97 tb(r)
835
    // the above is: codec, frame size, frame rate
836
    $pattern3 = "/Video: ([^,]+), ([0-9x]+)[^,]*, ([0-9\.]*)/";
837
838
    // did we find the video information on the first try ?
839
    if (preg_match($pattern1, $output, $matches)) {
840
      $file['video']['codec'] = !empty($matches[1]) ? $matches[1] : 'na';
841
      // $file['video']['type'] = $matches[2];
842
      $file['video']['s'] = !empty($matches[3]) ? $matches[3] : 'na';
843
      $file['video']['br'] = !empty($matches[4]) ? $matches[4] : 'na';
844
    }
845
    elseif (preg_match($pattern2, $output, $matches)) {
846
      $file['video']['codec'] = !empty($matches[1]) ? $matches[1] : 'na';
847
      $file['video']['s'] = !empty($matches[3]) ? $matches[3] : 'na';
848
      $file['video']['br'] = 'na';
849
    }
850
    elseif (preg_match($pattern3, $output, $matches)) {
851
      $file['video']['codec'] = !empty($matches[1]) ? $matches[1] : 'na';
852
      $file['video']['s'] = !empty($matches[3]) ? $matches[3] : 'na';
853
      $file['video']['br'] = 'na';
854
    }
855
    return $file;
856
  }
857
}
858
859
860
/**
861
 * Calculate an output size and a padding value for a video file.
862
 *
863
 * @param $file
864
 *   Path to the file to be converted.
865
 * @param $size
866
 *   The maximum dimensions of the output file, expressed as XXXxYYY. This will
867
 *   be cropped to match the original file's proportions and the remaining
868
 *   space will be used to calculate the padding.
869
 * @param $return
870
 *   Either 'padding' or 'size'.
871
 * @return
872
 *   Depending on the value of $return, the function returns either the size
873
 *   expressed as XXXxYYY, or the actual padding argument for FFmpeg, ie.
874
 *   "-padtop XX -padbottom XX". The result is statically cached, so you can
875
 *   call it multiple times without permormance issues.
876
 */
877
function ffmpeg_wrapper_padded_size($file, $size, $return = 'padding') {
878
  static $file_proportions;
879
880
  // Cache file proportions statically.
881
  if (!isset($file_proportions)) {
882
    $file_proportions = array();
883
  }
884
  if (isset($file_proportions[$file])) {
885
    return ($return == 'size') ? $file_proportions[$file][0] : $file_proportions[$file][1];
886
  }
887
888
  else {
889
    $pad = '';
890
891
    // Determine source file's dimensions and proportions.
892
    $info = ffmpeg_wrapper_file_data($file);
893
    if ($info && isset($info['video'])) {
894
      list($orig_x, $orig_y) = explode('x', $info['video']['s']);
895
      $orig_q = $orig_x / $orig_y;
896
897
      // Determine output dimensions and proportions.
898
      list($dest_x, $dest_y) = explode('x', $size);
899
      $dest_q = $dest_x / $dest_y;
900
901
      // Calculate new output size and padding.
902
      if ($orig_q > $dest_q) {
903
        // Width is the determining factor.
904
        $dest_y_calc = round($dest_x / $orig_q);
905
        // Make sure height is divisible by 2, otherwise ffmpeg freaks out.
906
        $dest_y_calc &= ~1;
907
        $size = $dest_x . 'x' . $dest_y_calc;
908
        $padding = $dest_y - $dest_y_calc;
909
        $padoptions = '-padtop %d -padbottom %d';
910
      }
911
      elseif ($dest_q > $orig_q) {
912
        // Height is the determining factor.
913
        $dest_x_calc = round($dest_y * $orig_q);
914
        // Make sure width is divisible by 2, otherwise ffmpeg freaks out.
915
        $dest_x_calc &= ~1;
916
        $size = $dest_x_calc . 'x' . $dest_y;
917
        $padding = $dest_x - $dest_x_calc;
918
        $padoptions = '-padleft %d -padright %d';
919
      }
920
921
      // Calculate padding on each side. Each value has to be a multiple of 2.
922
      $padding &= ~1;
923
      $padding1 = floor($padding / 2);
924
      $padding1 &= ~1;
925
      $padding2 = $padding - $padding1;
926
      $pad = sprintf($padoptions, $padding1, $padding2);
927
    }
928
929
    // Save and return the results.
930
    $file_proportions[$file] = array($size, $pad);
931
    return ($return == 'size') ? $size : $pad;
932
  }
933
}
934
935
936
/**
937
 * Check to make sure that FFmpeg is in the path.
938
 *
939
 * @return
940
 *   TRUE if FFmpeg can be executed, FALSE otherwise.
941
 */
942
function ffmpeg_wrapper_executable() {
943
  if (! ffmpeg_wrapper_run_command('')) {
944
    return false;
945
  }
946
  return true;
947
}
948
949
950
/**
951
 * Display a table of the supported ffmpeg file formats.
952
 *
953
 * @return
954
 *   The themed HTML form.
955
 */
956
function ffmpeg_wrapper_formats_data_display() {
957
  $header = array(t('name'), t('type'), t('decode'), t('encode'), t('description') );
958
  $output = theme('table', $header, ffmpeg_wrapper_get_file_formats() );
959
  return $output;
960
}
961
962
963
/**
964
 * Display a table of the ffmpeg encoding and decoding options.
965
 *
966
 * @return
967
 *   The themed HTML form.
968
 */
969
function ffmpeg_wrapper_get_codecs_display() {
970
  $header = array(t('codec'), t('codec type'), t('decode'), t('encode'));
971
  $output = theme('table', $header, ffmpeg_wrapper_get_codecs() );
972
  return $output;
973
}
974
975
976
/**
977
 * Check FFmpeg's output for errors and try to handle them some way.
978
 *
979
 * @param $ffmpeg_object
980
 *   Object containing all the data to check errors against
981
 *   - shell output and command run.
982
 * @param boolean $watchdog
983
 *   If TRUE, log errors to Drupal's watchdog.
984
 * @return
985
 *   TRUE if no errors, FALSE if errors.
986
 *
987
 **/
988
function ffmpeg_wrapper_error_check(&$ffmpeg_object, $watchdog = true) {
989
  $return = true;
990
991
  // build the error conditions these are all pulled by hand at this point
992
  // @NOTE one has to be careful to have a specific match as there are strings
993
  // in the output from #ffmpeg which containg "error"
994
  $errors = array(
995
    '/Segmentation fault .*/i',
996
    '/Unsupported .*/i',
997
    // match: bad formats
998
    '/Unknown format .*/i',
999
    '/Unable for find a suitable output format for .*/i',
1000
    '/Incorrect frame size .*/i',
1001
    '/Unsupported codec .*/i',
1002
    '/Could not write header .*/i',
1003
    '/already exists. Overwrite/i',
1004
    '/sh: [0-9a-zA-Z\/]*: not found .*/i',
1005
    '/no such file or directory .*/i',
1006
    '/.*does not support that sample rate.*/i',
1007
    // match: Error while opening codec for output stream #0.1 - maybe incorrect parameters such as bit_rate, rate, width or height
1008
    '/Error while opening codec for output stream.*/i',
1009
    // match: I/O error occured
1010
    '/I\/O error occured/i',
1011
    '/Failed to add video hook function*/i',
1012
    '/ffmpeg: unrecognized option*/i',
1013
    '/Could not open */i',
1014
  );
1015
1016
  // check for error conditions
1017
  foreach ($errors as $error) {
1018
    preg_match($error, $ffmpeg_object->output, $matches);
1019
    if (count($matches)) {
1020
      $ffmpeg_object->errors[] = print_r($matches[0], true);
1021
      if ($watchdog) {
1022
        $message = 'FFmpeg failed to convert a file. <b>FFmpeg said:</b> !error <br /><b>Command was:</b> ffmpeg !command <br /><b>Output was:</b> <pre>!output</pre>';
1023
        $variables = array('!error' => $matches[0], '!command' => $ffmpeg_object->command, '!output' => $ffmpeg_object->output);
1024
        watchdog('FFmpeg Wrapper', $message, $variables, WATCHDOG_ERROR);
1025
      }
1026
      return false;
1027
    }
1028
  }
1029
1030
  // check to see that a viable file was created
1031
  if (!empty($ffmpeg_object->output_file)) {
1032
    // check to see that the file had data
1033
    if (filesize($ffmpeg_object->output_file) < 10 ) {
1034
      $message = t('FFmpeg created a file, but it does not have any data');
1035
      $ffmpeg_object->errors[] = $message;
1036
      if ($watchdog) {
1037
        watchdog('FFmpeg', $message,  array(), WATCHDOG_ERROR);
1038
      }
1039
      $return = false;
1040
    }
1041
  }
1042
1043
  return $return;
1044
}
1045
1046
1047
/**
1048
 * Create a path to the called vhook library.
1049
 *
1050
 * @param $name
1051
 *   The name of the vhook library.
1052
 * @return
1053
 *   A full path.
1054
 */
1055
function ffmpeg_wrapper_path_to_vhook($name) {
1056
  $path = variable_get('ffmpeg_wrapper_vhook', '/usr/local/lib/vhook/') . $name;
1057
  if (file_exists($path)) {
1058
    return $path;
1059
  }
1060
}
1061
1062
/* ************************************************** */
1063
/* handle requests for configurations                 */
1064
/* ************************************************** */
1065
1066
/**
1067
 * Take an output format and return an array of configuration options.
1068
 * This is a hand built list. Will return default options below to preserve
1069
 * form integrity while switching things.
1070
 *
1071
 * @param $output
1072
 *   An output type (eg: flv, avi, mp4, etc).
1073
 * @return
1074
 *   Array of configuration options.
1075
 */
1076
function ffmpeg_wrapper_output_rules($output) {
1077
  // check to see if we have a configuration for this
1078
  $path = drupal_get_path('module', 'ffmpeg_wrapper');
1079
  if (file_exists($path .'/conf/'. $output .'.conf')) {
1080
    require_once($path .'/conf/'. $output .'.conf');
1081
    return $configuration;
1082
  }
1083
1084
  // we don't have a configuration setting, load up the defaults
1085
  // for any thing that we don't have data for. First check
1086
  // and see if we have a cache
1087
  $cache = cache_get('ffmpeg_wrapper_default_output');
1088
  if (is_array($cache->data)) {
1089
    return $cache->data;
1090
  }
1091
1092
  // no cache, build out the default options
1093
  $default['audio'] = array(
1094
    'ab' => ffmpeg_wrapper_output_rates('ab'),
1095
    'ar' => ffmpeg_wrapper_output_rates('ar'),
1096
    'acodec' => ffmpeg_wrapper_return_codecs('audio'),
1097
  );
1098
  $default['video'] = array(
1099
    'fps' => ffmpeg_wrapper_output_rates('fps'),
1100
    'br' =>  ffmpeg_wrapper_output_rates('br'),
1101
    'vcodec' => ffmpeg_wrapper_return_codecs('video'),
1102
  );
1103
  $default['default'] = 'default';
1104
  cache_set('ffmpeg_wrapper_default_output', $default, 'cache', CACHE_TEMPORARY);
1105
  return $default;
1106
}
1107
1108
/**
1109
 * Display the output rules as json.
1110
 *
1111
 * @param $output
1112
 *   An output type (eg: flv, avi, mp4, etc).
1113
 */
1114
function ffmpeg_wrapper_output_display($output) {
1115
  // get the output rules for this
1116
  if ($rules = ffmpeg_wrapper_output_rules($output)) {
1117
    // now build the JSON out
1118
    print(drupal_to_js($rules));
1119
  }
1120
  exit();
1121
}
1122
1123
1124
/**
1125
 * Load the js. This is a wrapper function just so other modules can use this.
1126
 *
1127
 * @param $prefix
1128
 *   The standard prefix to the elements that will be modified.
1129
 * @param $bind_element
1130
 *   The name of the element (minus the prefix) that will be modifed.
1131
 */
1132
function ffmpeg_wrapper_enable_js($prefix = '', $bind_element = '') {
1133
  // because of the way drupal handles form element ids, we have to transform
1134
  // underscores in string to dashes
1135
  $bind_element = str_replace('_', '-', $bind_element);
1136
  $prefix = str_replace('_', '-', $prefix);
1137
1138
  drupal_add_js('
1139
    $(document).ready(function () {
1140
      $(\'#'. $prefix . $bind_element .'\').bind("change", function () {ffmpeg_wrapper_update_options("'. $prefix .'", "'. $bind_element .'"); });
1141
    });',
1142
    'inline');
1143
  drupal_add_js(array('ffmpeg_wrapper' => array(
1144
    'ffmpeg_wrapper_output_url' => url('ffmpeg_wrapper/output/'),
1145
    'default_string' => t('default'),
1146
   )), 'setting');
1147
  drupal_add_js(drupal_get_path('module', 'ffmpeg_wrapper') .'/ffmpeg_wrapper.js');
1148
}
1149
1150
1151
/* ************************************************** */
1152
/* ffmpeg_wrapper forms                               */
1153
/* ************************************************** */
1154
1155
/**
1156
 * Build a generic form for any module to implementm ffmpeg configuration.
1157
 * This will give any module the ajax form configuration updates.
1158
 * Validation and submission need to be handled by the calling module - this
1159
 * only builds the form call this form inside your form function.
1160
 *
1161
 * @param $configuration
1162
 *   An array of configuration data - could be $form_values.
1163
 * @param $prefix
1164
 *   A prefix for the form elelements, needed for javascript activation on
1165
 *   complex forms (eg: media mover).
1166
 * @return array
1167
 *   A Drupal form array.
1168
 */
1169
function ffmpeg_wrapper_configuration_form($configuration = array(), $form_prefix = '' ) {
1170
1171
  // enable the javascript configuration options on the output type to use AJAX
1172
  // to update the allowed values
1173
  ffmpeg_wrapper_enable_js($form_prefix, 'ffmpeg_output_type');
1174
1175
  $form['ffmpeg_wrapper'] = array(
1176
    '#type' => 'fieldset',
1177
    '#title' => t("FFmpeg video conversion settings"),
1178
    '#collapsed' => false,
1179
  );
1180
1181
  // build the output formats
1182
  $form['ffmpeg_wrapper']['ffmpeg_output_type'] = array(
1183
    '#type' => 'select',
1184
    '#title' => t('Output format'),
1185
    '#options' => ffmpeg_wrapper_output_formats(),
1186
    '#default_value' => $configuration['ffmpeg_output_type'],
1187
    '#description' => t('Select the output format. Note, some formats may require setting audio or video codecs.'),
1188
  );
1189
1190
  // ---------------------------------------------
1191
  // Audio options
1192
  $form['ffmpeg_wrapper']['audio'] = array(
1193
    '#type' => 'fieldset',
1194
    '#title' => t('Audio conversion settings'),
1195
    '#collapsed' => $configuration['ffmpeg_audio_advanced'] ? false : true,
1196
    '#collapsible' => true,
1197
  );
1198
  $form['ffmpeg_wrapper']['audio']['ffmpeg_audio_advanced'] = array(
1199
    '#type' => 'checkbox',
1200
    '#title' => t('Use advanced settings'),
1201
    '#description' => t('Use the advanced audio encoding options. If this is off, FFmpeg will encode at a rate similar to the source material.'),
1202
    '#default_value' => $configuration['ffmpeg_audio_advanced'],
1203
  );
1204
  $form['ffmpeg_wrapper']['audio']["ffmpeg_audio_ab"] = array(
1205
    '#type' => 'select',
1206
    '#title' => t('Audio bit rate'),
1207
    '#options' => ffmpeg_wrapper_output_rates('ab'),
1208
    '#default_value' => $configuration['ffmpeg_audio_ab'] ? $configuration['ffmpeg_audio_ab'] :  array('64k'),
1209
    '#description' => t("Audio bit rate for conversion file."),
1210
  );
1211
  $form['ffmpeg_wrapper']['audio']["ffmpeg_audio_ar"] = array(
1212
    '#type' => 'select',
1213
    '#title' => t('Audio sample rate'),
1214
    '#options' => ffmpeg_wrapper_output_rates('ar'),
1215
    '#default_value' => $configuration['ffmpeg_audio_ar'] ? $configuration['ffmpeg_audio_ar'] : 44100,
1216
    '#description' => t('Audio sample rate for conversion.'),
1217
  );
1218
  // set the audio codec in use
1219
  $form['ffmpeg_wrapper']['audio']['ffmpeg_audio_acodec'] = array(
1220
    '#type' => 'select',
1221
    '#title' => t('Audio codec'),
1222
    '#options' => ffmpeg_wrapper_return_codecs('audio'),
1223
    '#default_value' => $configuration['ffmpeg_audio_acodec'] ? $configuration['ffmpeg_audio_acodec'] : 0 ,
1224
    '#description' => t('Select the codec for the output format. Please note, you may need to pick an appropriate video codec for the transcoding. WARNING the codec support right now is experimental. Things may not work as expected.'),
1225
  );
1226
1227
  // -------------------------------------------
1228
  // Video options
1229
  $form['ffmpeg_wrapper']['video'] = array(
1230
    '#type' => 'fieldset',
1231
    '#title' => t('Video conversion settings'),
1232
    '#collapsed' => $configuration['ffmpeg_video_advanced'] ? false : true,
1233
    '#collapsible' => true,
1234
  );
1235
  $form['ffmpeg_wrapper']['video']['ffmpeg_video_advanced'] = array(
1236
    '#type' => 'checkbox',
1237
    '#title' => t('Use advanced settings'),
1238
    '#description' => t('Use the advanced video encoding options. If this is off, FFmpeg will encode flv at 200 kb/s, 128x96, 25fps.'),
1239
    '#default_value' => $configuration['ffmpeg_video_advanced'],
1240
  );
1241
  // video frame size
1242
  $frame_size = ffmpeg_wrapper_frame_sizes();
1243
  $form['ffmpeg_wrapper']['video']['ffmpeg_video_size'] = array(
1244
    '#type' => 'select',
1245
    '#title' => t('Video frame size'),
1246
    '#options' => $frame_size,
1247
    '#default_value' => $configuration['ffmpeg_video_size'],
1248
    '#description' => t('Dimensions of the converted video. Use the <em>Other</em> option to define your custom size.'),
1249
    '#attributes' => array('class' => 'ffmpeg-video-size'),
1250
    '#validate' => array('mm_ffmpeg_config_video_size_validate' => array('ffmpeg_video_size')),
1251
  );
1252
  $form['ffmpeg_wrapper']['video']['ffmpeg_video_size_other'] = array(
1253
    '#type' => 'textfield',
1254
    '#title' => t('Video frame (Other) size'),
1255
    '#default_value' => $configuration['ffmpeg_video_size_other'],
1256
    '#description' => t('Dimensions of the converted video, of the format 600x400.'),
1257
    '#prefix' => '<div class="ffmpeg-video-size-other">',
1258
    '#suffix' => '</div>',
1259
    '#validate' => array('mm_ffmpeg_config_video_size_validate' => array('ffmpeg_video_size_other')),
1260
    '#size' => 12,
1261
  );
1262
  $form['ffmpeg_wrapper']['video']['ffmpeg_video_fps'] = array(
1263
    '#type' => 'select',
1264
    '#title' => t('Video fps rate'),
1265
    '#options' => ffmpeg_wrapper_output_rates('fps'),
1266
    '#default_value' => $configuration['ffmpeg_video_fps'] ? $configuration['ffmpeg_video_fps'] : array(25),
1267
    '#description' => t("Sets the frames per second of the converted video."),
1268
  );
1269
  $form['ffmpeg_wrapper']['video']['ffmpeg_video_br'] = array(
1270
    '#type' => 'select',
1271
    '#title' => t('Video bit rate'),
1272
    '#options' => ffmpeg_wrapper_output_rates('br'),
1273
    '#default_value' => $configuration['ffmpeg_video_br'] ? $configuration['ffmpeg_video_br'] : array('250k'),
1274
    '#description' => t('Target the output video to this bit rate.'),
1275
  );
1276
  $form['ffmpeg_wrapper']['video']['ffmpeg_video_vcodec'] = array(
1277
    '#type' => 'select',
1278
    '#title' => t('Video codec'),
1279
    '#options' => ffmpeg_wrapper_return_codecs('video'),
1280
    '#default_value' => $configuration['ffmpeg_video_vcodec'] ? $configuration['ffmpeg_video_vcodec'] : 0,
1281
    '#description' => t('Select the codec for the output format. Please note, you may need to pick an appropriate audio codec for the transcoding.'),
1282
  );
1283
1284
  $form['ffmpeg_wrapper']['time'] = array(
1285
    '#type' => 'fieldset',
1286
    '#title' => t('Video duration'),
1287
    '#collapsed' => $configuration['ffmpeg_time_advanced'] ? false : true,
1288
    '#collapsible' => true,
1289
  );
1290
  $form['ffmpeg_wrapper']['time']['ffmpeg_time_advanced'] = array(
1291
    '#type' => 'checkbox',
1292
    '#title' => t('Truncate the output video'),
1293
    '#description' => t('Use this to limit the length of the output video. Useful for making a video teaser'),
1294
    '#default_value' => $configuration['ffmpeg_time_advanced'],
1295
  );
1296
  $times = array(30 => "30 seconds", 60 => "1 minute", 120 => "2 minutes", 300 => "5 minutes");
1297
  $form['ffmpeg_wrapper']['time']["ffmpeg_time"] = array(
1298
    '#type' => 'select',
1299
    '#title' => t('Video length'),
1300
    '#options' => $times,
1301
    '#default_value' => $configuration['ffmpeg_time'],
1302
    '#description' => t("Set the max video length time."),
1303
  );
1304
1305
  // FFmpeg custom command
1306
  $form['ffmpeg_wrapper']['custom'] = array(
1307
    '#type' => 'fieldset',
1308
    '#title' => t('Custom FFmpeg command'),
1309
    '#collapsed' => $configuration['ffmpeg_video_custom'] ? false : true,
1310
    '#collapsible' => true,
1311
  );
1312
  $form['ffmpeg_wrapper']['custom']["ffmpeg_video_custom"] = array(
1313
    '#type' => 'checkbox',
1314
    '#title' => t('Use custom FFmpeg command'),
1315
    '#description' => t('Use the custom FFmpeg command in the textfield below.'),
1316
    '#default_value' => $configuration['ffmpeg_video_custom'],
1317
  );
1318
  $form['ffmpeg_wrapper']['custom']["ffmpeg_video_custom_command"] = array(
1319
    '#type' => 'textarea',
1320
    '#rows' => 1,
1321
    '#title' => t('Custom FFmpeg command'),
1322
    '#description' => t('You can craft your own FFmpeg command. Please see the FFmpeg documentation for correct syntax. The command will replace <em>%in_file</em> and <em>%out_file</em> with the generated files. Please note, you can not use | or > in your commands.'),
1323
    '#default_value' => $configuration['ffmpeg_video_custom_command'] ? $configuration['ffmpeg_video_custom_command'] : '-i %in_file %out_file',
1324
  );
1325
1326
  // Watermarking options
1327
  // only display if we have access to the watermarking files
1328
  if (ffmpeg_wrapper_path_to_vhook('watermark.so')) {
1329
    $form['ffmpeg_wrapper']['watermark'] = array(
1330
      '#type' => 'fieldset',
1331
      '#title' => t('Video watermark settings'),
1332
      '#collapsed' => $configuration['ffmpeg_video_wm'] ? false : true,
1333
      '#collapsible' => true,
1334
    );
1335
    $form['ffmpeg_wrapper']['watermark']['ffmpeg_video_wm'] = array(
1336
      '#type' => 'checkbox',
1337
      '#title' => t('Use watermark'),
1338
      '#description' => t('Use a file to watermark the converted video.'),
1339
      '#default_value' => $configuration['ffmpeg_video_wm'],
1340
    );
1341
    // @ TODO make this a file upload
1342
    $form['ffmpeg_wrapper']['watermark']['ffmpeg_video_wm_file'] = array(
1343
      '#type' => 'textfield',
1344
      '#title' => t('Path to file'),
1345
      '#description' => t('Absolute path to the watermark file.'),
1346
      '#default_value' => $configuration['ffmpeg_video_wm_file'],
1347
    );
1348
  }
1349
1350
  // file chmod settings, maybe legacy.....
1351
  $form['ffmpeg_wrapper']['file'] = array(
1352
    '#type' => 'fieldset',
1353
    '#title' => t('File settings'),
1354
    '#collapsed' => true,
1355
    '#collapsible' => true,
1356
  );
1357
  $form['ffmpeg_wrapper']['file']["ffmpeg_output_perms"] = array(
1358
    '#type' => 'textfield',
1359
    '#title' => t('Output file permissions'),
1360
    '#description' => t('Set the permissions on the output file. Default is 0644.'),
1361
    '#default_value' => $configuration['ffmpeg_output_perms'] ? $configuration['ffmpeg_output_perms'] : '0644' ,
1362
    '#size' => 4,
1363
  );
1364
1365
  return $form;
1366
}