1
<?php
2
3
4
/**
5
 * @FILE This file provides the transcoding testing capabilities
6
 *       for ffmpeg_wrapper
7
 */
8
9
10
/* ************************************************** */
11
/* TEST CONVERSION FORM FUNCTIONS                     */
12
/* ************************************************** */
13
14
15
/**
16
 * This creates a form for testing files. If data is being passed back into this
17
 * form it will use that data to attempt to convert the file.
18
 *
19
 * @NOTE This is a LARGE potential security risk. Only admins
20
 *       should have access to this function.
21
 *
22
 * @param string $path
23
 *   path to the file to manipulate
24
 * @param string $type
25
 *   type of item passing file (node, media_mover, etc)
26
 * @param int $id
27
 *   $id related to the $type, used to pass item back
28
 * @return array
29
 *   drupal form array
30
 */
31
function ffmpeg_wrapper_ffmpeg_test_form($form_state) {
32
  // If we have incoming files we are ready to process the files for conversion
33
  if (! empty($form_state['values'])) {
34
    $ffmpeg_object = $form_state['ffmpeg_object'];
35
    // set messages- we do this instead of drupal_message()
36
    $form['messages'] = array(
37
      '#type' => 'markup',
38
      '#value' => $ffmpeg_object->errors ? t('There were errors during the conversion!') : t('Your file was successfully transcoded!'),
39
      '#prefix' => '<h3>',
40
      '#suffix' => '</h3>',
41
    );
42
    // display the conversion output
43
    $form['data'] = array(
44
      '#type' => 'fieldset',
45
      '#title' => t('Conversion data'),
46
    );
47
    if ($ffmpeg_object->errors){
48
      $form['data']['errors'] = array(
49
        '#type' => 'markup',
50
        '#value' => t('Errors found: !errors', array('!errors' => implode('<br />', $ffmpeg_object->errors))),
51
        '#suffix' => '<br />',
52
      );
53
    }
54
    $form['data']['command'] = array(
55
      '#type' => 'markup',
56
      '#value' => t('<b>Command run was:</b> !command', array('!command' => $ffmpeg_object->command)),
57
      '#suffix' => '<br />',
58
    );
59
    $form['data']['output_file'] = array(
60
      '#type' => 'markup',
61
      '#value' => t('<b>Output file is:</b> !command', array('!command' => l($ffmpeg_object->output_file, file_create_url($ffmpeg_object->output_file)))),
62
      '#suffix' => '<br />',
63
    );
64
    $form['data']['ffmpeg_output'] = array(
65
      '#type' => 'fieldset',
66
      '#title' => t('FFmpeg Output'),
67
      '#collapsible' => true,
68
      '#collapsed' => true,
69
    );
70
    $form['data']['ffmpeg_output']['output'] = array(
71
      '#type' => 'markup',
72
      '#value' => '<pre>'. $ffmpeg_object->output .'</pre>',
73
    );
74
  }
75
76
  // header at the top of the page
77
  $form['info'] = array(
78
    '#type' => 'markup',
79
    '#value' => t('This allows you to test a configuration on a file that you specify as the input file. Additional files can be added in the directory: <br /> %path', array('%path' =>$path)),
80
  );
81
82
  // ------------------------------------------
83
  // AHAH form building
84
85
  // make sure we have files before we build the file data
86
  if ($files = ffmpeg_wrapper_test_build_files()) {
87
    // if FFmpeg was run, get the ID of the file so that
88
    // the form value for the radios can be set to the new file
89
    $output_file_id = ffmpeg_wrapper_test_file_id($files, $ffmpeg_object->output_file);
90
91
    $form['files'] = array(
92
      '#theme' => 'ffmpeg_wrapper_files_radios',
93
    );
94
95
    // build the radio form element that the admin can choose from to process a
96
    // file, option is tied to AHAH.
97
    $form['files']['data'] = array(
98
      '#type' => 'radios',
99
      '#default_value' => $output_file_id ? $output_file_id: $form_state['values']['files']['data'],
100
      '#options' => $files,
101
      '#suffix' => '<div id="ffmpeg_wrapper_file_data_display"></div>',
102
      '#ahah' => array(
103
        'event' => 'change',
104
        'path' => 'ffmpeg_wrapper/file_data',
105
        'wrapper' => 'ffmpeg_wrapper_file_data_display',
106
        'method' => 'replace',
107
        ),
108
    );
109
110
    // build the form elements for attaching a file back to a node
111
    $form['files']['attach_files'] = array(
112
      '#type' => 'radios',
113
      '#default_value' => $output_file_id ? $output_file_id: $form_state['values']['files']['attach'],
114
      '#options' => $files,
115
    );
116
117
    // build the options for the files and sub elements
118
    foreach ($files as $id => $file) {
119
      $file_name = $files[$id];
120
      $form['files'][$id]['name'] = array('#type' => 'markup',  '#value' => check_plain(basename($file_name)));
121
      $form['files'][$id]['size'] = array('#type' => 'markup',  '#value' => format_size(filesize($file_name)));
122
      $form['files'][$id]['mime'] = array('#type' => 'markup',  '#value' => file_get_mimetype($file_name));
123
    }
124
125
    $form['files_lookup'] = array(
126
      '#type' => 'value',
127
      '#value' => $files,
128
    );
129
130
  }
131
132
  // There were no files passed in, give the default path
133
  else {
134
    drupal_set_message(t('Sorry there are no files to test. Please place some files to test in !path',
135
      array('!path' => variable_get('ffmpeg_wrapper_test_test', drupal_get_path('module', 'ffmpeg_wrapper') .'/test'))));
136
    return;
137
  }
138
139
  // set the nid for the attach option
140
  $form['nid'] = array(
141
    '#type' => 'value',
142
    '#value' => $_GET['nid'] ? mysql_escape_string($_GET['nid']) : ($form_values['nid'] ? $form_values['nid'] : null),
143
  );
144
145
  // -----------------------------------------------------
146
  // FFmpeg form building
147
148
  // get the FFmpeg form
149
  $form['ffmpeg'] = ffmpeg_wrapper_configuration_form($form_state['values']);
150
151
  // add a submit button
152
  $form['submit'] = array(
153
    '#value' => t('Transcode'),
154
    '#type' => 'submit',
155
  );
156
157
  // add a submit button
158
  $form['attach'] = array(
159
    '#value' => t('Attach'),
160
    '#type' => 'submit',
161
    '#submit' => array('ffmpeg_wrapper_ffmpeg_test_file_attach'),
162
  );
163
164
  // This part is important!
165
  $form['#multistep'] = TRUE;
166
  $form['#redirect'] = FALSE;
167
168
  return $form;
169
}
170
171
172
/**
173
 * Checks to make sure data coming in is sane.
174
 * @param string $form_id
175
 * @param array $form_values
176
 */
177
function ffmpeg_wrapper_ffmpeg_test_form_validate($form_id, &$form_state) {
178
  // did the user transcode or attach the file?
179
  if ($form_state['values']['op'] == t('Transcode')) {
180
    // make sure an output type is set
181
    if (! $form_state['values']['ffmpeg_output_type']) {
182
      form_set_error('ffmpeg_output_type', t('You must specify an output type'));
183
    }
184
    // make sure a file was selected
185
    if (! isset($form_state['values']['data'])) {
186
      form_set_error('files', t('You must choose a file to transcode'));
187
    }
188
  }
189
190
  if ($form_state['clicked_button']['#post']['op'] == t('Attach')) {
191
    // get the path to the file
192
    $path = $form_state['values']['files_lookup'][$form_state['values']['attach_files']];
193
    // make sure we have an NID
194
    if (! $form_state['values']['nid']) {
195
      form_set_error('', t('Sorry, there is no node to attach this file back to.'));
196
    }
197
    // make sure a file was selected
198
    elseif (! isset($form_state['values']['attach_files'])) {
199
      form_set_error('attach', t('You must choose a file to attach'));
200
    }
201
    elseif(! file_exists($path)) {
202
      form_set_error('', t('The file you are trying to attach does not seem to exist. Maybe this was a bad transcode?'));
203
    }
204
    elseif(filesize($path) < 10) {
205
      form_set_error('', t('The file you are trying to attach a file does not seem to have data. Maybe this was a bad transcode?'));
206
    }
207
  }
208
}
209
210
function ffmpeg_wrapper_ffmpeg_test_form_submit($form, &$form_state) {
211
  // get the path to the file
212
  $path = $form_state['values']['files_lookup'][$form_state['values']['data']];
213
  // run the conversion process with the settings
214
  $form_state['ffmpeg_object'] = ffmpeg_wrapper_ffmpeg_test_form_run($form_state['values'], $path);
215
  // we will rebuild the form.
216
  $form_state['rebuild'] = TRUE;
217
}
218
219
220
/**
221
 * build the confirm form for removing all the files generated by the tests
222
 */
223
function ffmpeg_wrapper_test_cleanup_form() {
224
  $form['status'] = array(
225
    '#type' => 'markup',
226
    '#value' => t('When testing, FFmpeg Wrapper creates output files as well as copying input files. This allows you to remove them and reclaim disk space. '),
227
  );
228
  return confirm_form(
229
    $form,
230
    t('Are you sure you want to clean up the test directories? This will delete any of the test files that you have created.'),
231
    'admin/settings/ffmpeg_wrapper',
232
    t('This action cannot be undone.'),
233
    t('Delete'),
234
    t('Cancel')
235
  );
236
}
237
238
239
/**
240
 * deletes the test directories
241
 * @param string $form_id
242
 * @param array $form_values
243
 */
244
function ffmpeg_wrapper_test_cleanup_form_submit($form_id, &$form_state) {
245
  // set the paths for removal
246
  $paths = array(
247
    file_directory_path() .'/ffmpeg_wrapper_test_output',
248
    file_directory_path() .'/ffmpeg_wrapper_test_input',
249
  );
250
251
  // get a list of the files that are currently in the directories
252
  foreach ($paths as $path) {
253
    if (is_dir($path)) {
254
      $files = file_scan_directory($path, '.*$');
255
      if (count($files)) {
256
        foreach ($files as $file) {
257
          file_delete($file->filename);
258
        }
259
      }
260
      // now delete the directory
261
      rmdir($path);
262
    }
263
  }
264
265
  // clear the cached file
266
  cache_clear_all('ffmpeg_wrapper_test_file', 'cache');
267
  drupal_set_message(t('The test data has been deleted'));
268
  return 'admin/settings/ffmpeg_wrapper';
269
}
270
271
272
/* ************************************************** */
273
/* TEST CONVERSION FUNCTIONS                          */
274
/* ************************************************** */
275
276
277
/**
278
 * This function builds out the list of files used in the
279
 * landing form
280
 * @return array
281
 */
282
function ffmpeg_wrapper_test_build_files() {
283
  // -------------------------------------------------
284
  // Start building the normal form
285
  $paths = array();
286
  // set the path to our directories
287
  $paths['test'] = variable_get('ffmpeg_wrapper_test_test', drupal_get_path('module', 'ffmpeg_wrapper') .'/test');
288
  $paths['out'] = variable_get('ffmpeg_wrapper_test_out', file_directory_path() .'/ffmpeg_wrapper_test_output');
289
  $paths['in'] = variable_get('ffmpeg_wrapper_test_in', file_directory_path() .'/ffmpeg_wrapper_test_input');
290
291
  // build the file list from these directories
292
  // get a list of files that can be processed
293
  $files = array();
294
  foreach ($paths as $path) {
295
    $files = array_merge($files, file_scan_directory($path, '.*$'));
296
  }
297
298
  // get the files into shape for displaying
299
  if (count($files)) {
300
    // extract the filenames
301
    $return = array();
302
    foreach ($files as $file) {
303
      $return[] = check_plain($file->filename);
304
    }
305
  }
306
307
  // If we have an incoming file copy that has not matched other files
308
  // copy it into the test directory
309
  if ($file_path = mysql_escape_string($_GET['path'])) {
310
    if (! file_exists($file_path)) {
311
      drupal_set_message(t('Sorry, that file does not exist'), 'error');
312
    }
313
    else {
314
      // loop through the list of files and look for the requested file
315
      $found = false;
316
      if ($return) {
317
        foreach ($return as $file) {
318
          if (basename($file) == basename($file_path)) {
319
            // the file was found
320
            $found = true;
321
          }
322
        }
323
      }
324
      if (! $found ) {
325
        file_check_directory($paths['in'], FILE_CREATE_DIRECTORY);
326
        // we make a copy of the file for testing
327
        if (file_copy($file_path, $paths['in']) ) {
328
          drupal_set_message(t('Copied your file into the test directory'));
329
          // add the new file to the array
330
          $return[] = $file_path;
331
        }
332
        else {
333
          drupal_set_message(t('Could not copy your file into the test directory'), 'error');
334
        }
335
      }
336
    }
337
  }
338
  return $return;
339
}
340
341
342
/**
343
 * Get the ID of the incoming file from the $files array
344
 * if there is one?
345
 * @param $files
346
 *   array of files
347
 * @param $path
348
 *   path to look for
349
 * @return unknown_type
350
 */
351
function ffmpeg_wrapper_test_file_id($files = null, $path = null) {
352
  if ($files && $path) {
353
    foreach($files as $id => $file) {
354
      if ($path == $file) {
355
        return $id;
356
      }
357
    }
358
  }
359
  return false;
360
}
361
362
363
/**
364
 * This runs FFmpeg based on the form data passed into it.
365
 * @param string $input_file
366
 *   path to the file to operate on
367
 * @param array $params
368
 *   configuration options in the format set in the ffmpeg_wrapper_configuration_form()
369
 * @param string $output_file_path
370
 *   where to place the file, assumes same dir as $input_file. No trailing slash
371
 * @param object $ffmpeg_object
372
 *   contains debug information that calling functions can utilize
373
 * @return string
374
 *
375
 */
376
function ffmpeg_wrapper_configuration_form_convert($input_file, $params, $output_file_path = null, &$ffmpeg_object) {
377
  // first error check, make sure that we can decode this kind of file
378
  if (! ffmpeg_wrapper_can_decode($input_file)) {
379
    $message = 'FFmpeg Wrapper can not decode this file: !file';
380
    $variables = array('!file' => l($input_file, file_create_url($input_file)));
381
    watchdog('media_mover', $message, $variables, WATCHDOG_ERROR);
382
    $ffmpeg_object->errors[] = $message;
383
    return false;
384
  }
385
386
  // build the output file path if we don't have one. Use the output type as the extension.
387
  $output_file = file_create_filename(basename($input_file) .'.'. $params['ffmpeg_output_type'], ($output_file_path ? $output_file_path : dirname($input_file)));
388
389
  // did the admin define a specific FFmpeg comand to run?
390
  //  we only run what the admin specified
391
  if ($params['ffmpeg_video_custom']) {
392
    $options[] = str_replace(array('%in_file', '%out_file'), array($input_file, $output_file), $params['ffmpeg_video_custom_command']);
393
  }
394
  // build a standard configuration
395
  else {
396
    // build the ffmpeg command structure out
397
    $options = array();
398
399
    // input file
400
    $options[] = "-i '". $input_file ."'";
401
402
    // build the watermark config
403
    if ($params['ffmpeg_video_wm']) {
404
      $options[] = "-vhook '". ffmpeg_wrapper_path_to_vhook('watermark.so') ." -f ". $params['ffmpeg_video_wm_file'] ."'";
405
    }
406
407
    // build the audio config
408
    if ($params['ffmpeg_audio_advanced']) {
409
410
      // use a specifc codec?
411
      if ($params['ffmpeg_audio_acodec']) {
412
        $options[] =  '-acodec '. $params['ffmpeg_audio_acodec'];
413
      }
414
415
      // use a specific sample rate?
416
      if ($params['ffmpeg_audio_ar'] ) {
417
        $options[] = '-ar '. $params['ffmpeg_audio_ar'];
418
      }
419
420
      // use a specific bit rate?
421
      if ($params['ffmpeg_audio_ab']) {
422
        $options[] = '-ab '. $params['ffmpeg_audio_ab'];
423
      }
424
    }
425
426
    // build the video config
427
    if ($params['ffmpeg_video_advanced']) {
428
429
      // is codec set?
430
      if ($params['ffmpeg_video_vcodec']) {
431
        $options[] = '-vcodec '. $params['ffmpeg_video_vcodec'];
432
      }
433
434
      // is frame size set?
435
      if ($params['ffmpeg_video_size']) {
436
        $options[] = '-s '. $params[$params['ffmpeg_video_size'] == 'other' ? 'ffmpeg_video_size_other' : 'ffmpeg_video_size'];
437
      }
438
439
      // is the bit rate set?
440
      if ($params['ffmpeg_video_br']) {
441
        $options[] = '-b '. $params['ffmpeg_video_br'];
442
      }
443
444
      // is frame rate set?
445
      if ($params['ffmpeg_video_fps']) {
446
        $options[] = '-r '. $params['ffmpeg_video_fps'];
447
      }
448
    }
449
450
    // implement truncating
451
    if ($params['ffmpeg_time_advanced']) {
452
      $options[] = '-t '. $params['ffmpeg_time'];
453
    }
454
455
    // add the output file
456
    $options[] = "'". $output_file ."'";
457
  }
458
459
  $ffmpeg_object->command = implode(" ", $options);
460
461
  // run ffmpeg with error checking
462
  if (! $success = ffmpeg_wrapper_run_command($ffmpeg_object->command, true, null, $ffmpeg_object)) {
463
    return;
464
  }
465
466
  // successful convert, make a note in the log
467
  $message = 'FFmpeg converted this file: !file';
468
  $message .= '<br />'. 'FFmpeg ran this command: <br /><pre> !command </pre>';
469
  $variables = array('@file' => $output_file, !command =>  $ffmpeg_object->command);
470
  watchdog('FFmpeg', $message, $variables, WATCHDOG_NOTICE);
471
472
  $ffmpeg_object->output_file = $output_file;
473
474
  // return the completed file path
475
  return $output_file;
476
}
477
478
479
/**
480
 * This function is called after the form is submitted and when the form data returns
481
 * to the form function
482
 * @param $form_values
483
 * @return array
484
 */
485
function ffmpeg_wrapper_ffmpeg_test_form_run($form_values, $file_path) {
486
  $ffmpeg_object = new StdClass;
487
  // create a test directory if there is not already one
488
  $path = file_directory_path() .'/ffmpeg_wrapper_test_output';
489
  // make sure the directory exists
490
  file_check_directory($path, FILE_CREATE_DIRECTORY);
491
  // run ffmpeg on the file and configuration
492
  ffmpeg_wrapper_configuration_form_convert($file_path, $form_values, $path, $ffmpeg_object);
493
  return $ffmpeg_object;
494
}
495
496
497
/**
498
 * Takes the file chosen for attachment and attaches it to a node
499
 * @param $form_values
500
 * @return unknown_type
501
 */
502
function ffmpeg_wrapper_ffmpeg_test_file_attach($form_id, &$form_state) {
503
  // get the file to attach from the data
504
  $attach_file = $form_state['values']['files_lookup'][$form_state['values']['attach_files']];
505
  // load the node object
506
  $node = node_load($form_state['values']['nid']);
507
  // build the file object
508
  $file = new stdClass();
509
  $file->new = true;
510
  $file->fid = 'new';
511
  $file->uid = $node->uid;
512
  $file->filename = basename($attach_file);
513
  $file->filepath = file_create_filename(basename($attach_file), file_directory_path());
514
  $file->filemime = file_get_mimetype($attach_file);
515
  $file->filesize = filesize($attach_file);
516
  $file->timestamp = time();
517
  $file->list = variable_get('upload_list_default', 1);
518
  $file->status = 1;
519
  // save the file
520
  drupal_write_record('files', $file);
521
  // add the file to the node object
522
  $node->files[$file->fid] = $file;
523
  // save the modified node
524
  node_save($node);
525
  // alert the user
526
  drupal_set_message(t('Attached your transcoded file'));
527
  drupal_goto('node/'. $node->nid .'/edit');
528
}
529
530
531
/**
532
 * returns a json array of file type data
533
 * @return string
534
 */
535
function ffmpeg_wrapper_file_type_ahah() {
536
  // Get the cache form data
537
  $cached_form = form_get_cache($_POST['form_build_id'], &$cached_form_state);
538
539
  // Did we find a file that we can use?
540
  if ($file = $cached_form['files']['data']['#options'][$_POST['data']]) {
541
    if (! file_exists($file)){
542
      drupal_json(t('File does not exist'));
543
      exit();
544
    }
545
546
    // get the ffmpeg file data
547
    $file_data = ffmpeg_wrapper_file_data($file);
548
549
    // theme the output
550
    $meta = array(
551
      t('File: !file', array('!file' => check_plain(basename($file)))),
552
      t('Format: !format', array('!format' => $file_data['format'])),
553
      t('Duration: !duration (seconds)', array('!duration' => $file_data['duration'])),
554
      t('Total bitrate: !rate', array('!rate' => $file_data['bitrate'])),
555
    );
556
557
    $video = array(
558
      t('Video codec: !codec', array('!codec' => $file_data['video']['codec'])),
559
      t('Frame size: !s', array('!s' => $file_data['video']['s'])),
560
      t('Bit rate: !br', array('!br' => $file_data['video']['br'])),
561
      null,
562
    );
563
564
    $audio = array(
565
      t('Audio codec: !codec', array('!codec' => $file_data['audio']['codec'])),
566
      t('Sample rate: !ar', array('!ar' => $file_data['audio']['ar'])),
567
      t('Bit rate: !br', array('!br' => $file_data['audio']['ab'])),
568
      t('Chanels: !ac', array('!ac' => $file_data['audio']['ac'])),
569
    );
570
571
    // build the rows for the display
572
    $rows = array($meta, $video, $audio);
573
    $output = theme('table', array(t('File information')), $rows);
574
575
    // return the html in json format
576
    drupal_json($output);
577
    exit();
578
  }
579
}
580
581
/* ************************************************** */
582
/* TEST CONVERSION THEME FUNCTIONS                    */
583
/* ************************************************** */
584
585
/**
586
 * Themes the list of radio buttons and other form data
587
 * @param $form
588
 *   drupal form
589
 * @return unknown_type
590
 */
591
function theme_ffmpeg_wrapper_files_radios(&$form) {
592
  $rows = array();
593
  $header = array(t('File Name'), t('Use'), t('Attach'), t('Mime type'), t('Size'));
594
  // select each of the options from the $files form and build from the #options
595
  foreach (element_children($form['data']) as $key) {
596
    $row = array();
597
    // name of the file
598
    $row[] = drupal_render($form[$key]['name']);
599
    // render the radio
600
    unset($form['data'][$key]['#title']);
601
    $row[] = drupal_render($form['data'][$key]);
602
    // create the attach files radio, remove the title
603
    unset($form['attach_files'][$key]['#title']);
604
    $row[] = drupal_render($form['attach_files'][$key]);
605
    $row[] = drupal_render($form[$key]['mime']);
606
    $row[] = drupal_render($form[$key]['size']);
607
    $rows[] = $row;
608
  }
609
  $output = theme('table', $header, $rows);
610
  $output .= drupal_render($form);
611
  return $output;
612
}