nice = variable_get('video_ffmpeg_nice_enable', FALSE) ? 'nice -n 19 ' : ''; $this->thumbcmdoptions = variable_get('video_ffmpeg_thumbnailer_options', '-i !videofile -an -y -f mjpeg -ss !seek -vframes 1 !thumbfile'); $this->enablefaststart = variable_get('video_ffmpeg_enable_faststart', FALSE); $this->faststartcmd = variable_get('video_ffmpeg_faststart_cmd', '/usr/bin/qt-faststart'); $this->cmdpath = variable_get('video_transcoder_path', '/usr/bin/ffmpeg'); $this->logcommands = (bool)variable_get('video_ffmpeg_log_commands', TRUE); } /** * Run the specified command * * The nice prefix is automatically added. * The command is logged if the settings specify that all commands should be logged. * The command and error are logged if the command results in an error * * @param string $command * @param string $output Output of the command * @param string $purpose Purpose of the command. This is logged. * @param bool $ignoreoutputfilenotfound Whether to the output file not found error. Useful for input file information. */ private function run_command($command, &$output, $purpose = NULL, $ignoreoutputfilenotfound = FALSE) { $output = ''; $command = $this->nice . $command .' 2>&1'; $purposetext = !empty($purpose) ? (' '. t('for') .' '. $purpose) : ''; if ($this->logcommands) { watchdog('video_ffmpeg', 'Executing ffmpeg command!purposetext:
@command', array('@command' => $command, '!purposetext' => $purposetext), WATCHDOG_DEBUG); } $return_var = 0; ob_start(); passthru($command, &$return_var); $output = ob_get_clean(); // Returnvar 1 means input file not found. This is normal for information calls. if ($return_var != 0 && ($return_var != 1 || !$ignoreoutputfilenotfound)) { watchdog('video_ffmpeg', 'Error executing ffmpeg command!purposetext:
@commandOutput:
@output', array('@command' => $command, '@output' => trim($output), '!purposetext' => $purposetext), WATCHDOG_ERROR); return FALSE; } return TRUE; } public function generate_thumbnails($video) { global $user; $final_thumb_path = video_thumb_path($video); $total_thumbs = variable_get('video_thumbs', 5); $files = NULL; for ($i = 1; $i <= $total_thumbs; $i++) { $filename = '/video-thumb-for-'. $video['fid'] .'-'. $i .'.jpg'; $thumbfile = $final_thumb_path . $filename; //skip files already exists, this will save ffmpeg traffic if (!is_file($thumbfile)) { if (!isset($duration)) { $duration = $this->get_playtime($video['filepath']); } $seek = ($duration / $total_thumbs) * $i - 1; //adding minus one to prevent seek times equaling the last second of the video //setup the command to be passed to the transcoder. $command = $this->cmdpath .' '. strtr($this->thumbcmdoptions, array('!videofile' => escapeshellarg($video['filepath']), '!seek' => $seek, '!thumbfile' => $thumbfile)); // Generate the thumbnail from the video. $command_output = ''; $this->run_command($command, $command_output, t('generating thumbnails')); // don't consider zero-byte files a success $exists = file_exists($thumbfile); if (!$exists || filesize($thumbfile) == 0) { $params = array('%file' => $thumbfile, '%video' => $video['filename']); if ($exists) { $error_msg = 'Error generating thumbnail for video %video: generated file %file is empty. This problem may be caused by a broken video file. The video reports that its length is @duration seconds. If this is wrong, please recreate the video and try again.'; $params['@duration'] = $duration; unlink($thumbfile); } else { $error_msg = 'Error generating thumbnail for video %video: generated file %file does not exist.'; } // Log the error message and break. Other thumbnails probably will not be generated as well. watchdog('video_ffmpeg', $error_msg, $params, WATCHDOG_ERROR); drupal_set_message(t($error_msg, $params), 'error'); break; } } // Begin building the file object. // @TODO : use file_munge_filename() $file = new stdClass(); $file->uid = $user->uid; $file->status = FILE_STATUS_TEMPORARY; $file->filename = $filename; $file->filepath = $thumbfile; $file->filemime = 'image/jpeg'; $file->filesize = filesize($thumbfile); $file->timestamp = time(); $files[] = $file; } return $files; } public function convert_video($video) { // This will update our current video status to active. $this->change_status($video->vid, VIDEO_RENDERING_ACTIVE); // Get the converted file object //we are going to move our video to an "original" folder //we are going to transcode the video to the "converted" folder // @TODO This about getting the correct path from the filefield if they active it $files = file_create_path(); $originaldir = $files .'/videos/original'; $converteddir = $files .'/videos/converted'; if (!field_file_check_directory($originaldir, FILE_CREATE_DIRECTORY)) { watchdog('video_ffmpeg', 'Video conversion failed. Could not create directory %dir for storing original videos.', array('%dir' => $originaldir), WATCHDOG_ERROR); return false; } if (!field_file_check_directory($converteddir, FILE_CREATE_DIRECTORY)) { watchdog('video_ffmpeg', 'Video conversion failed. Could not create directory %dir for storing converted videos.', array('%dir' => $converteddir), WATCHDOG_ERROR); return false; } $original = $originaldir .'/'. $video->filename; //lets move our video and then convert it. if (!file_move($video, $original)) { watchdog('video_ffmpeg', 'Could not move video %orig to the original folder.', array('%orig' => $video->filepath), WATCHDOG_ERROR); $this->change_status($video->vid, VIDEO_RENDERING_FAILED); return FALSE; } // Update our filepath since we moved it drupal_write_record('files', $video, 'fid'); // Increase the database timeout to prevent database errors after a long upload _video_db_increase_timeout(); // Get video information before doing a chdir $dimensions = $this->dimensions($video); $dimension = explode('x', $dimensions, 2); $filepath = realpath($video->filepath); // Make the directories absolute $originaldir = realpath($originaldir); $converteddir = realpath($converteddir); // Create a temporary directory $drupaldir = getcwd(); $drupaldirrp = realpath($drupaldir); $tmpdir = tempnam(file_directory_temp(), 'ffmpeg-'. $video->fid); unlink($tmpdir); mkdir($tmpdir, 0777); chdir($tmpdir); $result = TRUE; // process presets $presets = $video->presets; $converted_files = array(); foreach ($presets as $presetname => $preset) { //update our filename after the move to maintain filename uniqueness. $converted = file_create_filename(str_replace(' ', '_', pathinfo($video->filepath, PATHINFO_FILENAME)) .'.'. $preset['extension'], $converteddir); //call our transcoder $ffmpeg_output = $converted; if ($this->enablefaststart && ($preset['extension'] == 'mp4' || $preset['extension'] == 'mov')) { $ffmpeg_output = file_directory_temp() .'/'. basename($converted); } $command_output = ''; // Setup our default command to be run. foreach ($preset['command'] as $command) { $command = strtr($command, array( '!cmd_path' => $this->cmdpath, '!videofile' => escapeshellarg($filepath), '!audiobitrate' => $preset['audio_bitrate'], '!width' => intval($dimension[0]), '!height' => intval($dimension[1]), '!videobitrate' => $preset['video_bitrate'], '!convertfile' => escapeshellarg($ffmpeg_output), )); // Process our video if (!$this->run_command($command, $command_output, t('rendering preset %preset', array('%preset' => $presetname)))) { $result = FALSE; break 2; } } if ($ffmpeg_output != $converted && file_exists($ffmpeg_output)) { // Because the transcoder_interface doesn't allow the run_command() to include the ability to pass // the command to be execute so we need to fudge the command to run qt-faststart. $command_result = $this->run_command($this->faststartcmd .' '. $ffmpeg_output .' '. $converted, $command_output, t('running qt-faststart')); // Delete the temporary output file. file_delete($ffmpeg_output); } //lets check to make sure our file exists, if not error out if (!file_exists($converted) || ($filesize = filesize($converted)) === 0) { watchdog('video_ffmpeg', 'Video conversion failed for preset %preset: result file was not found.', array('%preset' => $presetname), WATCHDOG_ERROR); $result = FALSE; break; } $converted = realpath($converted); //Create result object $converted_files[] = $file = new stdClass(); $file->vid = intval($video->vid); $file->filename = basename($converted); $file->filepath = substr($converted, strlen($drupaldirrp) + 1); $file->filemime = file_get_mimetype($converted); $file->filesize = $filesize; $file->preset = $presetname; } chdir($drupaldir); rmdirr($tmpdir); // Update our video_files table with the converted video information. if ($result) { $result = db_query('UPDATE {video_files} SET status = %d, completed = %d, data = "%s" WHERE vid = %d', VIDEO_RENDERING_COMPLETE, time(), serialize($converted_files), $video->vid); // Prepare the watchdog statement $destinationfiles = array(); foreach ($converted_files as $file) { $destinationfiles[] = $file->filepath; } watchdog('video_ffmpeg', 'Successfully converted %orig to !destination-files', array('%orig' => $video->filepath, '!destination-files' => implode(', ', $destinationfiles)), WATCHDOG_INFO); } else { // Remove files that have been created foreach ($converted_files as $file) { file_delete($file->filepath); } // Try to move back the original file file_move($video, $files .'/videos'); drupal_write_record('files', $video, 'fid'); $this->change_status($video->vid, VIDEO_RENDERING_FAILED); } return $result; } /** * Get some information from the video file */ private function get_video_info($video) { $command = $this->cmdpath .' -i '. escapeshellarg($video); // Execute the command $output = ''; if ($this->run_command($command, $output, t('retrieving video info'), true)) { return $output; } return NULL; } /** * Return the playtime seconds of a video */ public function get_playtime($video) { $ffmpeg_output = $this->get_video_info($video); // Get playtime $pattern = '/Duration: ([0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9])/'; preg_match_all($pattern, $ffmpeg_output, $matches, PREG_PATTERN_ORDER); $playtime = $matches[1][0]; // ffmpeg return length as 00:00:31.1 Let's get playtime from that $hmsmm = explode(':', $playtime); $tmp = explode('.', $hmsmm[2]); $seconds = $tmp[0]; $hours = $hmsmm[0]; $minutes = $hmsmm[1]; return $seconds + ($hours * 3600) + ($minutes * 60); } /* * Return the dimensions of a video */ public function get_dimensions($video) { $ffmpeg_output = $this->get_video_info($video); $res = array('width' => 0, 'height' => 0); // Get dimensions $regex = ereg('[0-9]?[0-9][0-9][0-9]x[0-9][0-9][0-9][0-9]?', $ffmpeg_output, $regs); if (isset($regs[0])) { $dimensions = explode("x", $regs[0]); $res['width'] = $dimensions[0] ? $dimensions[0] : NULL; $res['height'] = $dimensions[1] ? $dimensions[1] : NULL; } return $res; } /** * Interface Implementations * @see sites/all/modules/video/includes/transcoder_interface#get_name() */ public function get_name() { return $this->name; } /** * Interface Implementations * @see sites/all/modules/video/includes/transcoder_interface#get_value() */ public function get_value() { return $this->value; } /** * Interface Implementations * @see sites/all/modules/video/includes/transcoder_interface#get_help() */ public function get_help() { return l(t('FFMPEG Online Manual'), 'http://www.ffmpeg.org/'); } /** * Interface Implementations * @see sites/all/modules/video/includes/transcoder_interface#admin_settings() */ public function admin_settings() { $form = array(); $form['video_ffmpeg_start'] = array( '#type' => 'markup', '#value' => '