aboutsummaryrefslogtreecommitdiff
path: root/transcoders/video_phpvideotoolkit.inc
diff options
context:
space:
mode:
Diffstat (limited to 'transcoders/video_phpvideotoolkit.inc')
-rw-r--r--transcoders/video_phpvideotoolkit.inc627
1 files changed, 627 insertions, 0 deletions
diff --git a/transcoders/video_phpvideotoolkit.inc b/transcoders/video_phpvideotoolkit.inc
new file mode 100644
index 0000000..e97e100
--- /dev/null
+++ b/transcoders/video_phpvideotoolkit.inc
@@ -0,0 +1,627 @@
+<?php
+
+/*
+ * @file
+ * Transcoder class file to handle ffmpeg settings and conversions.
+ *
+ */
+
+class video_phpvideotoolkit implements transcoder_interface {
+
+ // Naming for our radio options. Makes it easy to extend our transcoders.
+ private $name = 'PHP Video Toolkit (FFMPEG)';
+ private $value = 'video_phpvideotoolkit';
+ protected $params = array();
+ protected $audio_bitrate = 64;
+ protected $video_bitrate = 200;
+ protected $video_width = 640;
+ protected $video_height = 480;
+ protected $ffmpeg = '/usr/bin/ffmpeg';
+ protected $nice;
+ protected $video_ext = 'flv';
+
+ protected $toolkit;
+
+ public function __construct() {
+ $this->params['audiobitrate'] = variable_get('video_ffmpeg_helper_auto_cvr_audio_bitrate', $this->audio_bitrate);
+ $this->params['videobitrate'] = variable_get('video_ffmpeg_helper_auto_cvr_video_bitrate', $this->video_bitrate);
+ //@todo: move this to the actual widget and save in video_files table.
+ $this->params['size'] = variable_get('video_ffmpeg_width', $this->video_width) . 'x' . variable_get('video_ffmpeg_height', $this->video_height);
+ $this->params['cmd_path'] = variable_get('video_transcoder_path', $this->ffmpeg);
+ $this->nice = variable_get('video_ffmpeg_nice_enable', false) ? 'nice -n 19 ' : '';
+ $this->params['videoext'] = variable_get('video_ffmpeg_ext', $this->video_ext);
+ $this->params['enable_faststart'] = variable_get('video_ffmpeg_enable_faststart', 0);
+ $this->params['faststart_cmd'] = variable_get('video_ffmpeg_faststart_cmd', '/usr/bin/qt-faststart');
+
+ $use_version = 'php5';
+ // check if php5 is ok
+ if($use_version == 'php5' && version_compare(PHP_VERSION, '5.0.0', '<'))
+ {
+ $use_version = 'php4';
+ }
+
+ module_load_include('php','video','libraries/phpvideotoolkit/phpvideotoolkit.'.$use_version);
+
+ $this->toolkit = new PHPVideoToolkit($this->params['cmd_path'], file_directory_temp().'/');
+ }
+
+ // Returns an array of available encoding & decoding codecs
+ public function get_codecs() {
+ $info = $this->toolkit->getFFmpegInfo(false);
+
+ $available_codecs = $info['codecs'];
+
+ $codecs = array('decode' => array(), 'encode' => array());
+
+ foreach($available_codecs as $key => $value) {
+ $codecs['encode'][$key] = array();
+ $codecs['decode'][$key] = array();
+
+ foreach($value as $codec_key => $codec) {
+ if ($codec['encode']) {
+ $codecs['encode'][$key][$codec_key] = $codec['fullname'];
+ }
+ if ($codec['decode']) {
+ $codecs['decode'][$key][$codec_key] = $codec['fullname'];
+ }
+ }
+ }
+
+ return $codecs;
+ }
+
+ public function generate_thumbnails($video) {
+ global $user;
+ // Setup our thmbnail path.
+ $video_thumb_path = variable_get('video_thumb_path', 'videos/thumbnails');
+ // Get the file system directory.
+ $schema_thumb_path = file_default_scheme() . '://' . $video_thumb_path . '/' . $video['fid'];
+ file_prepare_directory($schema_thumb_path, FILE_CREATE_DIRECTORY);
+ // Total thumbs to generate
+ $total_thumbs = variable_get('video_thumbs', 5);
+ $videofile = file_load($video['fid']);
+ //get the actual video file path from the stream wrappers
+ $videopath = drupal_realpath($videofile->uri);
+ //get the playtime from the current transcoder
+ $duration = $this->get_playtime($videopath);
+
+ $files = NULL;
+ for ($i = 1; $i <= $total_thumbs; $i++) {
+ $seek = ($duration / $total_thumbs) * $i - 1; //adding minus one to prevent seek times equaling the last second of the video
+ $filename = file_munge_filename("video-thumb-for-" . $video['fid'] . "-$i.jpg", '', TRUE);
+ $thumbfile = $schema_thumb_path . '/' . $filename;
+ //skip files already exists, this will save ffmpeg traffic
+ if (!is_file(drupal_realpath($thumbfile))) {
+ $result = $this->toolkit->setInputFile($videopath);
+ if(!$result)
+ {
+// if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+
+ $this->toolkit->extractFrame($seek);
+
+ $result = $this->toolkit->setOutput(drupal_realpath($schema_thumb_path).'/', $filename, PHPVideoToolkit::OVERWRITE_EXISTING);
+ if(!$result)
+ {
+// if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+
+ $result = $this->toolkit->execute(false, true);
+ if($result !== PHPVideoToolkit::RESULT_OK)
+ {
+// if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+
+ if (!file_exists(drupal_realpath($thumbfile))) {
+ $error_param = array('%file' => $thumbfile);
+ $error_msg = t("Error generating thumbnail for video: generated file %file does not exist.", $error_param);
+ // Log the error message.
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ continue;
+ }
+ }
+ // Begin building the file object.
+ // @TODO : use file_munge_filename()
+ $file = new stdClass();
+ $file->uid = $user->uid;
+ $file->status = 0;
+ $file->filename = trim($filename);
+ $file->uri = $thumbfile;
+ $file->filemime = file_get_mimetype($filename);
+ $file->filesize = filesize(drupal_realpath($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 paths so tokens will compatible with this
+ // @todo : add best method to get existing file path and add converted there
+ $target = str_replace('original', '', drupal_dirname($video->uri));
+ $converted_base_dir = $target . 'converted/' . $video->fid;
+ if (!file_prepare_directory($converted_base_dir, FILE_CREATE_DIRECTORY)) {
+ watchdog('transcoder', 'Video conversion failed. Could not create the directory: ' . $converted_base_dir, array(), WATCHDOG_ERROR);
+ return FALSE;
+ }
+ //get the actual video file path from the stream wrappers
+ $original_video_path = drupal_realpath($video->uri);
+ // process presets
+ $presets = $video->presets;
+ $converted_files = array();
+ foreach ($presets as $name => $preset) {
+ $settings = $preset['settings'];
+ // override with preset settings
+ if (isset($settings['width']) && !empty($settings['width']) && isset($settings['height']) && !empty($settings['height'])
+ && variable_get('video_use_preset_wxh', FALSE)) {
+ $video->dimensions = $settings['width'] . 'x' . $settings['height'];
+ }
+ $converted_filename = file_munge_filename(str_replace(' ', '_', pathinfo($original_video_path, PATHINFO_FILENAME)) . '.' . $settings['video_extension'], $settings['video_extension']);
+ $converted = $converted_base_dir . '/' . $converted_filename;
+ //get the actual video file path from the stream wrappers
+ $converted_video_path = drupal_realpath($converted);
+ $dimensions = $this->dimensions($video);
+ $dimension = explode('x', $dimensions);
+
+ $video_info = $this->get_video_info($original_video_path);
+
+ if ($this->params['enable_faststart'] && in_array($settings['video_extension'], array('mov', 'mp4'))) {
+ $ffmpeg_output = file_directory_temp() . '/' . basename($converted_video_path);
+ } else {
+ $ffmpeg_output = $converted_video_path;
+ }
+
+ $result = $this->toolkit->setInputFile($original_video_path);
+ if(!$result)
+ {
+// if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+
+ if (!empty($settings['max_frame_rate'])) {
+ $result = $this->toolkit->setVideoFrameRate($settings['max_frame_rate']);
+ if(!$result)
+ {
+ // if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+ }
+
+ $result = $this->toolkit->setVideoCodec($settings['video_codec'],false);
+ if(!$result)
+ {
+// if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+
+ $settings['audio_sample_rate'] = (!empty($settings['audio_sample_rate'])) ? $settings['audio_sample_rate'] : $video_info['audio']['sample_rate'];
+
+ if ($settings['audio_sample_rate'] < 1000) {
+ $settings['audio_sample_rate'] *= 1000;
+ }
+
+ $settings['audio_sample_rate'] = min($settings['audio_sample_rate'],44100);
+
+ $result = $this->toolkit->setAudioSampleFrequency($settings['audio_sample_rate']);
+ if(!$result)
+ {
+// if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+
+ $result = $this->toolkit->setAudioCodec($settings['audio_codec'],false);
+ if(!$result)
+ {
+// if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+
+ $result = $this->toolkit->setAudioChannels($settings['audio_channels']);
+ if(!$result)
+ {
+// if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+
+ if (empty($settings['audio_bitrate'])) {
+ $settings['audio_bitrate'] = $this->audio_bitrate;
+ }
+
+ if ($settings['audio_bitrate'] < 1000) {
+ $settings['audio_bitrate'] *= 1000;
+ }
+
+ $result = $this->toolkit->setAudioBitRate($settings['audio_bitrate']);
+ if(!$result)
+ {
+// if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+
+ if (empty($settings['video_bitrate'])) {
+ $settings['video_bitrate'] = $this->video_bitrate;
+ }
+
+ if ($settings['video_bitrate'] < 1000) {
+ $settings['video_bitrate'] *= 1000;
+ }
+
+ $result = $this->toolkit->setVideoBitRate($settings['video_bitrate']);
+ if(!$result)
+ {
+// if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+
+ $result = $this->toolkit->setVideoDimensions($dimension[0],$dimension[1]);
+ if(!$result)
+ {
+// if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+
+ $result = $this->toolkit->setOutput(dirname($ffmpeg_output).'/',$converted_filename, PHPVideoToolkit::OVERWRITE_EXISTING);
+ if(!$result)
+ {
+// if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+
+ $result = $this->toolkit->execute(false, true);
+ if($result !== PHPVideoToolkit::RESULT_OK)
+ {
+// if there was an error then get it
+ $error_msg = t($this->toolkit->getLastError());
+ watchdog('transcoder', $error_msg, array(), WATCHDOG_ERROR);
+ $this->toolkit->reset();
+ continue;
+ }
+
+ $command_output = $this->toolkit->getLastOutput();
+/*
+ if ($ffmpeg_output != $converted_video_path && 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.
+ $cmd_path = $this->params['cmd_path'];
+ $this->params['cmd_path'] = $this->params['faststart_cmd'];
+ $command_output .= $this->run_command($ffmpeg_output . ' ' . $converted_video_path, $verbose);
+ $this->params['cmd_path'] = $cmd_path;
+
+ // Delete the temporary output file.
+ drupal_unlink($ffmpeg_output);
+ }
+*/
+ //lets check to make sure our file exists, if not error out
+ if (!file_exists($converted_video_path) || !filesize($converted_video_path)) {
+ watchdog('transcoder', 'Video conversion failed for preset %preset. FFMPEG reported the following output: ' . $command_output, array('%orig' => $video->uri, '%preset' => $name), WATCHDOG_ERROR);
+ $this->change_status($video->vid, VIDEO_RENDERING_FAILED);
+ return FALSE;
+ }
+ // Setup our converted video object
+ $video_info = pathinfo($converted_video_path);
+ //update our converted video
+ $video->converted = new stdClass();
+ $video->converted->vid = $video->vid;
+ $video->converted->filename = $video_info['basename'];
+ $video->converted->uri = $converted;
+ $video->converted->filemime = file_get_mimetype($converted);
+ $video->converted->filesize = filesize($converted);
+ $video->converted->status = VIDEO_RENDERING_COMPLETE;
+ $video->converted->preset = $name;
+ $video->converted->completed = time();
+ $converted_files[] = $video->converted;
+ }
+
+ // Update our video_files table with the converted video information.
+ db_update('video_files')
+ ->fields(array(
+ 'status' => VIDEO_RENDERING_COMPLETE,
+ 'completed' => time(),
+ 'data' => serialize($converted_files)))
+ ->condition('vid', $video->converted->vid, '=')
+ ->execute();
+ watchdog('transcoder', 'Successfully converted %orig to %dest', array('%orig' => $video->uri, '%dest' => $video->converted->uri), WATCHDOG_INFO);
+ return TRUE;
+ }
+
+ /**
+ * Get some information from the video file
+ */
+ public function get_video_info($video) {
+ $video_info = $this->toolkit->getFileInfo($video);
+ return $video_info;
+ }
+
+ /**
+ * Return the playtime seconds of a video
+ */
+ public function get_playtime($video) {
+ $video_info = $this->get_video_info($video);
+
+ return $video_info['duration']['seconds'];
+ }
+
+ /*
+ * Return the dimensions of a video
+ */
+
+ public function get_dimensions($video) {
+ $video_info = $this->get_video_info($video);
+ $res = array('width' => 0, 'height' => 0);
+ // Get dimensions
+ $res['width'] = $video_info['video']['dimensions']['width'] ? $video_info['video']['dimensions']['width'] : NULL;
+ $res['height'] = $video_info['video']['dimensions']['height'] ? $video_info['video']['dimensions']['height'] : 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();
+ return $form;
+ }
+
+ /**
+ * Interface Implementations
+ * @see sites/all/modules/video/includes/transcoder_interface#admin_settings_validate()
+ */
+ public function admin_settings_validate($form, &$form_state) {
+ return;
+ }
+
+ /**
+ * Interface Implementations
+ * @see sites/all/modules/video/includes/transcoder_interface#create_job()
+ */
+ public function create_job($video, $nid) {
+ return db_insert('video_files')
+ ->fields(array(
+ 'fid' => $video['fid'],
+ 'nid' => $nid,
+ 'status' => VIDEO_RENDERING_PENDING,
+ 'dimensions' => $video['dimensions'],
+ ))
+ ->execute();
+ }
+
+ /**
+ * Interface Implementations
+ * @see sites/all/modules/video/includes/transcoder_interface#delete_job()
+ */
+ public function delete_job($video) {
+ $video = (object) $video;
+ if (!$video = $this->load_job($video->fid))
+ return;
+ // converted output values
+ $converted = unserialize($video->data);
+ if (!empty($converted)) {
+ foreach ($converted as $file) {
+ if (file_exists(drupal_realpath($file->uri)))
+ @drupal_unlink($file->uri);
+ }
+ }
+ //now delete our rows.
+ db_delete('video_files')
+ ->condition('fid', $video->fid)
+ ->execute();
+ }
+
+ /**
+ * Interface Implementations
+ * @see sites/all/modules/video/includes/transcoder_interface#load_job()
+ */
+ public function load_job($fid) {
+ $job = null;
+ $job = db_query("SELECT f.*, vf.vid, vf.nid, vf.dimensions, vf.data, vf.status as video_status
+ FROM {video_files} vf LEFT JOIN {file_managed} f ON vf.fid = f.fid WHERE f.fid=vf.fid AND f.fid = :fid",
+ array(':fid' => $fid))
+ ->fetch();
+ if (!empty($job))
+ return $job;
+ else
+ return FALSE;
+ }
+
+ /**
+ * Interface Implementations
+ * @see sites/all/modules/video/includes/transcoder_interface#load_job_queue()
+ */
+ public function load_job_queue() {
+ $total_videos = variable_get('video_ffmpeg_instances', 5);
+ $videos = array();
+ $result = db_query_range('SELECT f.*, vf.vid, vf.nid, vf.dimensions, vf.status as video_status
+ FROM {video_files} vf LEFT JOIN {file_managed} f ON vf.fid = f.fid
+ WHERE vf.status = :vstatus AND f.status = :fstatus ORDER BY f.timestamp',
+ 0, $total_videos, array(':vstatus' => VIDEO_RENDERING_PENDING, ':fstatus' => FILE_STATUS_PERMANENT));
+ foreach ($result as $row) {
+ $videos[] = $row;
+ }
+ return $videos;
+ }
+
+ /**
+ * Interface Implementations
+ * @see sites/all/modules/video/includes/transcoder_interface#load_completed_job()
+ */
+ public function load_completed_job(&$video) {
+ $file = $this->load_job($video->fid);
+ $data = unserialize($file->data);
+ if (!empty($data))
+ foreach ($data as $value) {
+ $extension = pathinfo(drupal_realpath($value->uri), PATHINFO_EXTENSION);
+ $video->files->{$extension}->filename = $value->filename;
+ $video->files->{$extension}->filepath = $value->uri;
+ $video->files->{$extension}->filemime = file_get_mimetype($value->uri);
+ $video->files->{$extension}->url = file_create_url($value->uri);
+ $video->files->{$extension}->extension = $extension;
+ $video->player = strtolower($extension);
+ }
+ else
+ return FALSE;
+ }
+
+ /**
+ * Change the status of the file.
+ *
+ * @param (int) $vid
+ * @param (int) $status
+ */
+ public function change_status($vid, $status) {
+ db_update('video_files')->fields(array(
+ 'status' => $status,))
+ ->condition('vid', $vid, '=')
+ ->execute();
+ }
+
+ /*
+ * Function determines the dimensions you want and compares with the actual wxh of the video.
+ *
+ * If they are not exact or the aspect ratio does not match, we then figure out how much padding
+ * we should add. We will either add a black bar on the top/bottom or on the left/right.
+ *
+ * @TODO I need to look more at this function. I don't really like the guess work here. Need to implement
+ * a better way to check the end WxH. Maybe compare the final resolution to our defaults? I don't think
+ * that just checking to make sure the final number is even is accurate enough.
+ */
+
+ public function dimensions($video) {
+ //lets setup our dimensions. Make sure our aspect ratio matches the dimensions to be used, if not lets add black bars.
+ $aspect_ratio = _video_aspect_ratio(drupal_realpath($video->uri));
+ $ratio = $aspect_ratio['ratio'];
+ $width = $aspect_ratio ['width'];
+ $height = $aspect_ratio['height'];
+
+ $wxh = explode('x', $video->dimensions);
+ $output_width = $wxh[0];
+ $output_height = $wxh[1];
+ $output_ratio = number_format($output_width / $output_height, 4);
+
+ if ($output_ratio != $ratio && $width && $height) {
+ $options = array();
+ // Figure out our black bar padding.
+ if ($ratio < $output_width / $output_height) {
+ $end_width = $output_height * $ratio;
+ $end_height = $output_height;
+ } else {
+ $end_height = $output_width / $ratio;
+ $end_width = $output_width;
+ }
+
+ // We need to get back to an even resolution and maybe compare with our defaults?
+ // @TODO Make this more exact on actual video dimensions instead of making sure the wxh are even numbers
+
+ if ($end_width == $output_width) {
+ // We need to pad the top/bottom of the video
+ $padding = round($output_height - $end_height);
+ $pad1 = $pad2 = floor($padding / 2);
+ if ($pad1 % 2 !== 0) {
+ $pad1++;
+ $pad2--;
+ }
+ if (variable_get('video_ffmpeg_pad_method', 0)) {
+ $options[] = '-vf "pad=' . round($output_width) . ':' . round($output_height) . ':0:' . $pad1 . '"';
+ } else {
+ $options[] = '-padtop ' . $pad1;
+ $options[] = '-padbottom ' . $pad2;
+ }
+ } else {
+ // We are padding the left/right of the video.
+ $padding = round($output_width - $end_width);
+ $pad1 = $pad2 = floor($padding / 2); //@todo does padding need to be an even number?
+ if ($pad1 % 2 !== 0) {
+ $pad1++;
+ $pad2--;
+ }
+ if (variable_get('video_ffmpeg_pad_method', 0)) {
+ $options[] = '-vf "pad=' . round($output_width) . ':' . round($output_height) . ':' . $pad1 . ':0"';
+ } else {
+ $options[] = '-padleft ' . $pad1;
+ $options[] = '-padright ' . $pad2;
+ }
+ }
+
+ $end_width = round($end_width) % 2 !== 0 ? round($end_width) + 1 : round($end_width);
+ $end_height = round($end_height) % 2 !== 0 ? round($end_height) + 1 : round($end_height);
+ //add our size to the beginning to make sure it hits our -s
+ array_unshift($options, $end_width . 'x' . $end_height);
+ return implode(' ', $options);
+ } else {
+ return $video->dimensions;
+ }
+ }
+
+}
+
+?>