| 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 |
} |