From b6e86040dd3faa3a70ec16e77220d852bdb09a04 Mon Sep 17 00:00:00 2001 From: Heshan Wanigasooriya Date: Sun, 5 Dec 2010 12:56:20 +0000 Subject: Adding latest files. --- .cvsignore | 2 + INSTALL.txt | 65 + README.txt | 24 + css/.cvsignore | 1 + css/video.css | 134 ++ filesystem/.cvsignore | 1 + filesystem/drupal.inc | 70 + images/.cvsignore | 1 + images/no-thumb.png | Bin 0 -> 25315 bytes images/play.png | Bin 0 -> 6951 bytes includes/.cvsignore | 1 + includes/conversion.inc | 117 ++ includes/filesystem.inc | 129 ++ includes/metadata.inc | 118 ++ includes/preset.inc | 106 ++ includes/transcoder.inc | 205 +++ includes/video_helper.inc | 116 ++ js/.cvsignore | 1 + js/flowplayer-3.2.0.min.js | 24 + js/jquery.media.js | 458 +++++++ js/jquery.metadata.js | 148 +++ js/video.js | 122 ++ metadata/.cvsignore | 1 + metadata/flvtool2.inc | 86 ++ plugins/.cvsignore | 1 + plugins/video_s3/filesystem/video_s3.inc | 183 +++ plugins/video_s3/includes/S3.php | 1365 ++++++++++++++++++++ plugins/video_s3/includes/amazon_s3.inc | 169 +++ plugins/video_s3/video_s3.info | 8 + plugins/video_s3/video_s3.install | 107 ++ plugins/video_s3/video_s3.module | 168 +++ plugins/video_zencoder/includes/LICENSE | 20 + plugins/video_zencoder/includes/README.markdown | 199 +++ plugins/video_zencoder/includes/Zencoder.php | 240 ++++ plugins/video_zencoder/includes/lib/JSON.php | 806 ++++++++++++ plugins/video_zencoder/includes/zencoder.inc | 254 ++++ .../video_zencoder/transcoders/video_zencoder.inc | 382 ++++++ plugins/video_zencoder/video_zencoder.info | 9 + plugins/video_zencoder/video_zencoder.install | 135 ++ plugins/video_zencoder/video_zencoder.module | 152 +++ theme/.cvsignore | 1 + theme/video-play-dcr.tpl.php | 17 + theme/video-play-divx.tpl.php | 26 + theme/video-play-flash.tpl.php | 20 + theme/video-play-flv.tpl.php | 14 + theme/video-play-html5.tpl.php | 32 + theme/video-play-quicktime.tpl.php | 32 + theme/video-play-realmedia.tpl.php | 22 + theme/video-play-theora.tpl.php | 23 + theme/video-play-windowsmedia.tpl.php | 21 + transcoders/.cvsignore | 1 + transcoders/video_ffmpeg.inc | 551 ++++++++ translations/.cvsignore | 1 + translations/de.po | 560 ++++++++ translations/video.pot | 548 ++++++++ types/.cvsignore | 1 + types/uploadfield/uploadfield.info | 9 + types/uploadfield/uploadfield.install | 25 + types/uploadfield/uploadfield.module | 146 +++ types/uploadfield/uploadfield.theme.inc | 18 + types/uploadfield/uploadfield_widget.inc | 129 ++ types/videoftp/videoftp.info | 9 + types/videoftp/videoftp.install | 25 + types/videoftp/videoftp.module | 207 +++ types/videoftp/videoftp.theme.inc | 57 + types/videoftp/videoftp_widget.inc | 313 +++++ video.admin.inc | 183 +++ video.drush.inc | 30 + video.info | 9 + video.install | 142 ++ video.module | 979 ++++++++++++++ video.theme.inc | 105 ++ video_formatter.inc | 197 +++ video_preset/.cvsignore | 1 + video_preset/hq_flash.inc | 77 ++ video_preset/html5_mp4.inc | 79 ++ video_preset/html5_ogv.inc | 77 ++ video_preset/html5_webm.inc | 78 ++ video_preset/iphone_mov.inc | 76 ++ video_scheduler.php | 137 ++ views/.cvsignore | 1 + views/video.views.inc | 80 ++ views/video_views_handler_field_data.inc | 60 + 83 files changed, 11247 insertions(+) create mode 100644 INSTALL.txt create mode 100644 README.txt create mode 100644 css/.cvsignore create mode 100644 css/video.css create mode 100644 filesystem/.cvsignore create mode 100644 filesystem/drupal.inc create mode 100644 images/.cvsignore create mode 100644 images/no-thumb.png create mode 100644 images/play.png create mode 100644 includes/.cvsignore create mode 100644 includes/conversion.inc create mode 100644 includes/filesystem.inc create mode 100644 includes/metadata.inc create mode 100644 includes/preset.inc create mode 100644 includes/transcoder.inc create mode 100644 includes/video_helper.inc create mode 100644 js/.cvsignore create mode 100644 js/flowplayer-3.2.0.min.js create mode 100644 js/jquery.media.js create mode 100644 js/jquery.metadata.js create mode 100644 js/video.js create mode 100644 metadata/.cvsignore create mode 100644 metadata/flvtool2.inc create mode 100644 plugins/.cvsignore create mode 100644 plugins/video_s3/filesystem/video_s3.inc create mode 100644 plugins/video_s3/includes/S3.php create mode 100644 plugins/video_s3/includes/amazon_s3.inc create mode 100644 plugins/video_s3/video_s3.info create mode 100644 plugins/video_s3/video_s3.install create mode 100644 plugins/video_s3/video_s3.module create mode 100644 plugins/video_zencoder/includes/LICENSE create mode 100644 plugins/video_zencoder/includes/README.markdown create mode 100644 plugins/video_zencoder/includes/Zencoder.php create mode 100644 plugins/video_zencoder/includes/lib/JSON.php create mode 100644 plugins/video_zencoder/includes/zencoder.inc create mode 100644 plugins/video_zencoder/transcoders/video_zencoder.inc create mode 100644 plugins/video_zencoder/video_zencoder.info create mode 100644 plugins/video_zencoder/video_zencoder.install create mode 100644 plugins/video_zencoder/video_zencoder.module create mode 100644 theme/.cvsignore create mode 100644 theme/video-play-dcr.tpl.php create mode 100644 theme/video-play-divx.tpl.php create mode 100644 theme/video-play-flash.tpl.php create mode 100644 theme/video-play-flv.tpl.php create mode 100644 theme/video-play-html5.tpl.php create mode 100644 theme/video-play-quicktime.tpl.php create mode 100644 theme/video-play-realmedia.tpl.php create mode 100644 theme/video-play-theora.tpl.php create mode 100644 theme/video-play-windowsmedia.tpl.php create mode 100644 transcoders/.cvsignore create mode 100644 transcoders/video_ffmpeg.inc create mode 100644 translations/.cvsignore create mode 100644 translations/de.po create mode 100644 translations/video.pot create mode 100644 types/.cvsignore create mode 100644 types/uploadfield/uploadfield.info create mode 100644 types/uploadfield/uploadfield.install create mode 100644 types/uploadfield/uploadfield.module create mode 100644 types/uploadfield/uploadfield.theme.inc create mode 100644 types/uploadfield/uploadfield_widget.inc create mode 100644 types/videoftp/videoftp.info create mode 100644 types/videoftp/videoftp.install create mode 100644 types/videoftp/videoftp.module create mode 100644 types/videoftp/videoftp.theme.inc create mode 100644 types/videoftp/videoftp_widget.inc create mode 100644 video.admin.inc create mode 100644 video.drush.inc create mode 100644 video.info create mode 100644 video.install create mode 100644 video.module create mode 100644 video.theme.inc create mode 100644 video_formatter.inc create mode 100644 video_preset/.cvsignore create mode 100644 video_preset/hq_flash.inc create mode 100644 video_preset/html5_mp4.inc create mode 100644 video_preset/html5_ogv.inc create mode 100644 video_preset/html5_webm.inc create mode 100644 video_preset/iphone_mov.inc create mode 100644 video_scheduler.php create mode 100644 views/.cvsignore create mode 100644 views/video.views.inc create mode 100644 views/video_views_handler_field_data.inc diff --git a/.cvsignore b/.cvsignore index dc9c5f0..3ba64fc 100644 --- a/.cvsignore +++ b/.cvsignore @@ -1,3 +1,5 @@ +.DS_Store_1 +.git .cvsignore_1 .DS_Store .project diff --git a/INSTALL.txt b/INSTALL.txt new file mode 100644 index 0000000..208a038 --- /dev/null +++ b/INSTALL.txt @@ -0,0 +1,65 @@ +// $Id$ + +REQUIREMENTS +------------------------------------ +Required : + CCK Module. + Filefield Module. + + + +INSTALL INSTRUCTIONS FOR VIDEO.MODULE +------------------------------------- +- See : http://video.heidisoft.com/content/welcome-video-module-documentation + + +MIGRATE INSTRUCTIONS TO VIDEO.MODULE +------------------------------------- +- See : http://drupal.org/node/713482 + + +FFMPEG CONFIGURATIONS +--------------------- + +This helper module facilitates uploading new videos using the video module. It +features a batch processing queue for videos to be transcoded and automatic +thumbnail generation. + +Install instructions +-------------------- + +1. Activate the auto conversion and auto thumbnails when creating content +2. Setup it's advanced options to meet your needs +3. Move (or symlink) video_scheduler.php into your Drupal root +4. Check permissions of the files and folders (/tmp/video and files/* must be +writable by the webserver or the user executling the cron job) +5. Schedule the execution of video_scheduler.php using unix cron: + + The crontab should look something like this: + + # m h dom mon dow user command + */20 * * * * www-data cd /absolute/path/to/drupal/ ; php video_scheduler.php http://www.example.com/path_to_drupal + + This will execute the video_scheduler every 20 minutes. + + Note that the video_scheduler doesn't produce any output and cannot be called + from the web. It will, however, put some information in the watchdog. + + + +Troubleshooting +------------------------ + +Configuring and installing ffmpeg in a web server environment might be pretty +difficult. In order to help you troubleshoot the transcoding process the ffmpeg +helper puts debugging informations on the drupal logs. I strongly suggest to +have a look at them if you are experiencing problems with transcoding. + +The ffmpeg puts in the drupal logs the commands it was trying to execute. You +might try to rerun them on a command shell in order understand what went wrong. + +Or contact a Drupal developer + + + + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..4b4cdaf --- /dev/null +++ b/README.txt @@ -0,0 +1,24 @@ +// $Id$ +------------ + + to embed videos into drupal pages, using CCK and filefield support. +This module add the possibility to create video nodes which are containers + to embed videos into drupal pages, using CCK and filefield support. + +For information about supported file types see FILE_TYPES.txt +http://video.heidisoft.com/content/welcome-video-module-documentation + +For general instructions read video.module handbook: +http://video.heidisoft.com/content/features + +Please submit bugs/features/support requests at: + http://video.heidisoft.com/content/welcome-video-module-documentation + http://video.heidisoft.com/contact + + +Maintainers +----------- + + Heshan Wanigasooriya :heshan at heidisoft dot com, heshanmw at gmail dot com + Dennis : http://drupal.org/user/384543 + Glen Marianko : http://drupal.org/usr/527446 http://linkedin.com/in/glenergetic twitter: @glenergetic \ No newline at end of file diff --git a/css/.cvsignore b/css/.cvsignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/css/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/css/video.css b/css/video.css new file mode 100644 index 0000000..f369145 --- /dev/null +++ b/css/video.css @@ -0,0 +1,134 @@ +/* $Id$ */ + +.filefield-element .filefield-file-info, .video_thumbnail { + min-width: 100px; + min-height: 100px; +} + +.filefield-element .imagefield-preview { + min-width: 100px; + min-height: 100px; +} + +.filefield-element .imagefield-text { + max-width: 40em; /* Reflect the maxlength of the ALT attribute (80 characters). */ +} + +.filefield-element .uploadfield-text { + max-width: 40em; +} + +.video_thumbnail { + min-width: 100px; + min-height: 100px; +} + +.uploadfield { + +} + +.uploadfield-nodelink { + +} + +.uploadfield-video-thumb { + +} + +.uploadfield-video-thumb img{ + width : 250px; +/* min-width: 100px; */ + min-height: 210px; + +} + +.uploadfield-video-thumb span{ + /* This is the overlay image for use as a play button. */ + background: url(../images/play.png) no-repeat; + + /* This is the size of our button. DOES NOT WORK WITH DEFAULT THUMB SIZE! */ + width: 50px; + height: 112px; + position : absolute; + margin : 65px 100px; +} + +.uploadfield-video-thumb span:hover { + background-position: -50px 0px; +} + +.video-width-text { + width:10px; +} + +.video-thumb-selection{ + +} + +.video-thumb-selection .form-item { + +} + +.video-thumbnails{ +/* display : block;*/ +} +.video-thumbnails .form-item{ + float:left; + margin-right:10px; + max-width:30%; + padding-right:10px; +} + +.video-bypass-auto-conversion{ + +} +.video-default-thumbnail{ + +} + +.video-data{ + +} + +.video_image_teaser { + float: left; + padding: 0.5em; +} + +br.video_image_clear { + clear: both; +} + +.video_image_view { + /* inser here rules for node page image */ +} + +.video-ffmpeg-helper-inprogress { + border: 1px solid red; + padding: .5em; +} + +/* override filefield entry */ +.widget-edit{ + max-width:70%; +} + +.widget-edit .form-item { white-space: normal !important; } + +.admin_flv_player_wrapper { display: none; } +/* + * VIDEO OJBECT FIXES CROSS BROWSER + */ +* html object.video-object { + display: none; +} + +* html object.video-object/**/ { + display: inline; +} + +* html object.video-object { + display/**/: none; +} + + \ No newline at end of file diff --git a/filesystem/.cvsignore b/filesystem/.cvsignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/filesystem/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/filesystem/drupal.inc b/filesystem/drupal.inc new file mode 100644 index 0000000..d85490d --- /dev/null +++ b/filesystem/drupal.inc @@ -0,0 +1,70 @@ +name; + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/filesystem_interface#get_help() + */ + public function get_help() { + return t('Drupal filesystem', array()); + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/filesystem_interface#get_value() + */ + public function get_value() { + return $this->value; + } + + public function run_command($options) { + return; + } + + public function admin_settings() { + $form = array(); + return $form; + } + + public function admin_settings_validate($form, $form_state) { + return; + } + +} + +?> diff --git a/images/.cvsignore b/images/.cvsignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/images/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/images/no-thumb.png b/images/no-thumb.png new file mode 100644 index 0000000..3d86209 Binary files /dev/null and b/images/no-thumb.png differ diff --git a/images/play.png b/images/play.png new file mode 100644 index 0000000..1a61c7b Binary files /dev/null and b/images/play.png differ diff --git a/includes/.cvsignore b/includes/.cvsignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/includes/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/includes/conversion.inc b/includes/conversion.inc new file mode 100644 index 0000000..021640b --- /dev/null +++ b/includes/conversion.inc @@ -0,0 +1,117 @@ +transcoder = new video_transcoder; + } + + /** + * Our main function to call when converting queued up jobs. + */ + public function run_queue() { + if ($videos = $this->load_job_queue()) { + foreach ($videos as $video) { + $this->process($video); + } + //clear cache once completed the conversion to update the file paths + cache_clear_all('*', 'cache_content', true); + } + } + + /** + * Select videos from our queue + * + * @return + * An array containing all the videos to be proccessed. + */ + private function load_job_queue() { + // @TODO : allow only limited jobs to process + return $this->transcoder->load_job_queue(); + } + + /** + * Process the video through ffmpeg. + * + * @param $video + * This can either be the file object or the file id (fid) + * + * @return + * TRUE of FALSE if video was converted successfully. + */ + public function process($video) { + if (is_object($video) && isset($video->fid)) { + $return = $this->render($video); + } else { + $video_object = $this->load_job($video); + $return = $this->render($video_object); + } + return $return; + } + + private function render($video) { + if (!is_object($video)) { + watchdog('video_conversion', 'Video object is not present', array(), WATCHDOG_ERROR); + return FALSE; + } + // Make sure this video is pending or do nothing. + if ($video->video_status == VIDEO_RENDERING_PENDING) { + return $this->transcoder->convert_video($video); + } + return NULL; + } + + /** + * Load a converted video based on the file id ($fid) + * + * @todo: Need to figure something out here for multiple files (HTML 5) + * @param $fid + * Integer of the file id to be loaded. + */ + public function load_completed_job($video) { + return $this->transcoder->load_completed_job($video); + } + + public function create_job($video) { + return $this->transcoder->create_job($video); + } + + public function update_job($video) { + return $this->transcoder->update_job($video); + } + + public function delete_job($video) { + return $this->transcoder->delete_job($video); + } + + /** + * Load a file based on the file id ($fid) + * + * @param $fid + * Integer of the file id to be loaded. + */ + public function load_job($fid) { + return $this->transcoder->load_job($fid); + } + +} + +?> \ No newline at end of file diff --git a/includes/filesystem.inc b/includes/filesystem.inc new file mode 100644 index 0000000..3041ee5 --- /dev/null +++ b/includes/filesystem.inc @@ -0,0 +1,129 @@ +name == $filesystem) + require_once $file->filename; + } + } + } + } + if (class_exists($filesystem)) { + $this->filesystem = new $filesystem; + } else { + drupal_set_message(t('The filesystem is not configured properly.'), 'error'); + } + } + + public function save_file($video) { + return $this->filesystem->save_file($video); + } + + public function prepare_file($video) { + return $this->filesystem->prepare_file($video); + } + + public function load_file(&$video) { + return $this->filesystem->load_file($video); + } + + public function admin_settings() { + $form = array(); + $options = $this->_filesystem(); + $form['vid_filesystem'] = array( + '#type' => 'radios', + '#title' => t('Video Filesystem'), + '#default_value' => variable_get('vid_filesystem', 'drupal'), + '#options' => $options['radios'], + '#description' => t('!list', array('!list' => theme('item_list', $options['help']))), + '#prefix' => '
', + '#suffix' => '
', + ); + $form = $form + $options['admin_settings']; + return $form; + } + + private function _filesystem() { + $files = array(); + // Lets find our transcoder classes and build our radio options + // We do this by scanning our transcoders folder + $form = array('radios' => array(), 'help' => array(), 'admin_settings' => array()); + $path = drupal_get_path('module', 'video') . '/filesystem'; + $files = file_scan_directory($path, '^.*\.inc$'); + // check inside sub modules + $modules = module_list(); + foreach ($modules as $module) { + $mobule_files = array(); + $module_path = drupal_get_path('module', $module) . '/filesystem'; + $mobule_files = file_scan_directory($module_path, '^.*\.inc$'); + $files = array_merge($files, $mobule_files); + } + + foreach ($files as $file) { + if (!module_load_include('inc', 'video', '/filesystem/' . $file->name)) + require_once $file->filename; + $focus = new $file->name; + $form['radios'][$focus->get_value()] = $focus->get_name(); + $form['help'][] = $focus->get_help(); + // creating div for each option + $form['video_' . $focus->get_value() . '_start'] = array( + 'video_' . $focus->get_value() . '_start' => array( + '#type' => 'markup', + '#value' => '
', + ), + ); + $form['video_' . $focus->get_value() . '_end'] = array( + 'video_' . $focus->get_value() . '_end' => array( + '#type' => 'markup', + '#value' => '
', + ), + ); + + $form['admin_settings'] = $form['admin_settings'] + $form['video_' . $focus->get_value() . '_start'] + $focus->admin_settings() + $form['video_' . $focus->get_value() . '_end']; + } + return $form; + } + + public function admin_settings_validate(&$form, &$form_state) { + return $this->filesystem->admin_settings_validate($form, $form_state); + } + +} + +interface filesystem_interface { + + public function save_file($video); + + public function prepare_file($video); + + public function load_file($video); + + public function get_name(); + + public function get_help(); + + public function admin_settings(); + + public function admin_settings_validate($form, &$form_state); +} \ No newline at end of file diff --git a/includes/metadata.inc b/includes/metadata.inc new file mode 100644 index 0000000..42f80b7 --- /dev/null +++ b/includes/metadata.inc @@ -0,0 +1,118 @@ +name == $metadata) + require_once $file->filename; + } + } +// + } + } + if (class_exists($metadata)) { + $this->metadata = new $metadata; + } else { + drupal_set_message(t('The metadata is not configured properly.'), 'error'); + } + } + + public function process($video) { + $command_output = $this->metadata->process($video); + return $command_output; + } + + public function admin_settings() { + $form = array(); + $form['video_metadata'] = array( + '#type' => 'checkbox', + '#title' => t('Enable Metadata'), + '#default_value' => variable_get('video_metadata', FALSE), + '#description' => t('Metadata is particularly useful in video, where information about its contents (such as transcripts of conversations and text descriptions of its scenes) are not directly understandable by a computer, but where efficient search is desirable.'), + ); + $options = $this->_metadata(); + $form['vid_metadata'] = array( + '#type' => 'radios', + '#title' => t('Video Metadata'), + '#default_value' => variable_get('vid_metadata', 'flvtool2'), + '#options' => $options['radios'], + '#description' => t('!list', array('!list' => theme('item_list', $options['help']))), + '#prefix' => '
', + '#suffix' => '
', + ); + $form = $form + $options['admin_settings']; + $form['video_metadata_dimensions'] = array( + '#type' => 'textarea', + '#title' => t('Selectable Dimensions when uploading videos'), + '#description' => t('Enter one dimension per line as Video Resolutions. Each resolution must be in the form of WxH where W=Width and H=Height in pixels. Example dimensions are 1280x720.'), + '#default_value' => variable_get("video_metadata_dimensions", video_default_dimensions()), + ); + + return $form; + } + + private function _metadata() { + $files = array(); + // Lets find our transcoder classes and build our radio options + // We do this by scanning our transcoders folder + $form = array('radios' => array(), 'help' => array(), 'admin_settings' => array()); + $path = drupal_get_path('module', 'video') . '/metadata'; + $files = file_scan_directory($path, '^.*\.inc$'); + // check inside sub modules + $modules = module_list(); + foreach ($modules as $module) { + $mobule_files = array(); + $module_path = drupal_get_path('module', $module) . '/metadata'; + $mobule_files = file_scan_directory($module_path, '^.*\.inc$'); + $files = array_merge($files, $mobule_files); + } + + foreach ($files as $file) { + if (!module_load_include('inc', 'video', '/metadata/' . $file->name)) + require_once $file->filename; + $focus = new $file->name; + $form['radios'][$focus->get_value()] = $focus->get_name(); + $form['help'][] = $focus->get_help(); + $form['admin_settings'] = $form['admin_settings'] + $focus->admin_settings(); + } + return $form; + } + + public function admin_settings_validate($form, $form_state) { + return $this->metadata->admin_settings_validate($form, $form_state); + } + +} + +interface metadata_interface { + + public function get_name(); + + public function get_help(); + + public function process($video); + + public function admin_settings(); + + public function admin_settings_validate($form, &$form_state); +} \ No newline at end of file diff --git a/includes/preset.inc b/includes/preset.inc new file mode 100644 index 0000000..aca0b26 --- /dev/null +++ b/includes/preset.inc @@ -0,0 +1,106 @@ +presets = $preset; + if (!isset($preset)) + $this->presets = variable_get('vid_preset', ''); +//get our configured transcoder. +// if (!isset($preset)) +// $preset = variable_get('vid_preset', 'flash_hq'); +// echo print_r($preset); +// if (!module_load_include('inc', 'video', '/video_preset/' . $preset)) { +// $modules = module_list(); +// foreach ($modules as $module) { +// $mobule_files = array(); +// $module_path = drupal_get_path('module', $module) . '/video_preset'; +// $mobule_files = file_scan_directory($module_path, '^.*\.inc$'); +// if (is_array($mobule_files)) { +// foreach ($mobule_files as $file) { +// if ($file->name == $preset) +// require_once $file->filename; +// } +// } +//// +// } +// } +// if (class_exists($preset)) { +// $this->preset = new $preset; +// } else { +// drupal_set_message(t('The preset is not configured properly.'), 'error'); +// } + } + + public function admin_settings() { + $form = array(); + $options = $this->_preset(); + $form['vid_preset'] = array( + '#type' => 'checkboxes', + '#title' => t('Video transcode presets'), + '#options' => $options['radios'], + '#default_value' => variable_get('vid_preset', array('status', 'promote')), + '#description' => t('!list', array('!list' => theme('item_list', $options['help']))), + '#prefix' => '
', + '#suffix' => '
', + ); +// $form = $form + $options['admin_settings']; + return $form; + } + + private function _preset($presets = null) { +// @TDOO : Observer will match in this case + $files = array(); +// Lets find our transcoder classes and build our radio options +// We do this by scanning our transcoders folder + $form = array('radios' => array(), 'help' => array(), 'properties' => array()); + $path = drupal_get_path('module', 'video') . '/video_preset'; + $files = file_scan_directory($path, '^.*\.inc$'); +// check inside sub modules + $modules = module_list(); + foreach ($modules as $module) { + $mobule_files = array(); + $module_path = drupal_get_path('module', $module) . '/video_preset'; + $mobule_files = file_scan_directory($module_path, '^.*\.inc$'); + $files = array_merge($files, $mobule_files); + } + + foreach ($files as $file) { + if (!module_load_include('inc', 'video', '/video_preset/' . $file->name)) + require_once $file->filename; + $focus = new $file->name; + $form['radios'][$focus->get_value()] = $focus->get_name(); + $form['help'][] = $focus->get_help(); +// $form['admin_settings'] = $form['admin_settings'] + $focus->admin_settings(); + if (is_array($presets) && !empty($presets[$focus->get_value()])) + $form['properties'][$focus->get_value()] = $focus->get_properties(); +// echo $focus->get_value(); + } + return $form; + } + + public function properties() { + $presets = $this->presets; + $options = $this->_preset($presets); + return $options['properties']; + } + +} + +interface video_preset_interface { + + public function get_name(); + + public function get_help(); + + public function get_properties(); +} \ No newline at end of file diff --git a/includes/transcoder.inc b/includes/transcoder.inc new file mode 100644 index 0000000..c9d79b8 --- /dev/null +++ b/includes/transcoder.inc @@ -0,0 +1,205 @@ +transcoder = $this->get_instance($transcoder); + } + + /** + * + * @param $transcoder + */ + private function get_instance($transcoder = null) { + //get our configured transcoder. + if (!isset($transcoder)) + $transcoder = variable_get('vid_convertor', 'video_ffmpeg'); +// module_load_include('inc', 'video', '/transcoders/' . $transcoder); + if (!module_load_include('inc', 'video', '/transcoders/' . $transcoder)) { + $modules = module_list(); + $files = array(); + foreach ($modules as $module) { + $module_path = drupal_get_path('module', $module) . '/transcoders'; + $inc_files = file_scan_directory($module_path, '^.*\.inc$'); + if (!empty($inc_files)) + $files[$module] = $inc_files; + } + // @TODO : add lazy load + foreach ($files as $module => $_files) { + foreach ($_files as $file) { + if ($file->name == $transcoder) + module_load_include('inc', $module, '/transcoders/' . $file->name); + } + } + } + + if (class_exists($transcoder)) { + $transcoder_instance = new $transcoder; + $this->transcoder = $transcoder_instance; + return $transcoder_instance; + } else { + drupal_set_message(t('The transcoder is not configured properly.'), 'error'); + } + } + + public function generate_thumbnails($video) { + return $this->transcoder->generate_thumbnails($video); + } + + public function convert_video(&$video) { + module_load_include('inc', 'video', '/includes/preset'); + $video_preset = new video_preset(); + $presets = $video_preset->properties(); + $video->presets = $presets; + $output = $this->transcoder->convert_video($video); + // if successfully converted the video then update the status to publish + if ($output) + // Update our node id to published. We do not do a node_load as it causes editing problems when saving. + db_query("UPDATE {node} SET status=%d WHERE nid=%d", 1, $video->nid); + + // If they are using metadata. + // @TODO : add meta data support +// if (variable_get('video_metadata', FALSE)) { +// module_load_include('inc', 'video', '/includes/metadata'); +// $metadata = new video_metadata; +// $metadata->process($converted); +// } + return $output; + } + + public function admin_settings() { + $form = array(); + $options = $this->_transcoders(); + $form['vid_convertor'] = array( + '#type' => 'radios', + '#title' => t('Video transcoder'), + '#default_value' => variable_get('vid_convertor', 'video_ffmpeg'), + '#options' => $options['radios'], + '#description' => t('Selecting a video transcoder will help you convert videos and generate thumbnails. !list', array('!list' => theme('item_list', $options['help']))), + '#prefix' => '
', + '#suffix' => '
', + ); + $form = $form + $options['admin_settings']; + return $form; + } + + public function admin_settings_validate($form, &$form_state) { + return $this->transcoder->admin_settings_validate($form, $form_state); + } + + private function _transcoders() { + // @TODO : think to change this to observer patteren + $files = array(); + // Lets find our transcoder classes and build our radio options + // We do this by scanning our transcoders folder + $form = array('radios' => array(), 'help' => array(), 'admin_settings' => array()); + $path = drupal_get_path('module', 'video') . '/transcoders'; + $files = file_scan_directory($path, '^.*\.inc$'); + // check inside sub modules + $modules = module_list(); + foreach ($modules as $module) { + $mobule_files = array(); + $module_path = drupal_get_path('module', $module) . '/transcoders'; + $mobule_files = file_scan_directory($module_path, '^.*\.inc$'); + $files = array_merge($files, $mobule_files); + } + + foreach ($files as $file) { + if (!module_load_include('inc', 'video', '/transcoders/' . $file->name)) + require_once $file->filename; + $focus = new $file->name; + $form['radios'][$focus->get_value()] = $focus->get_name(); + $form['help'][] = $focus->get_help(); + $form['admin_settings'] = $form['admin_settings'] + $focus->admin_settings(); + } +// //we need to move our video/thumbnail fieldsets to the bottom of our form as they are used for each trancoder +// $autothumb = $form['admin_settings']['autothumb']; +// $autoconv = $form['admin_settings']['autoconv']; +// unset($form['admin_settings']['autothumb'], $form['admin_settings']['autoconv']); +// if(!$this->transcoder->is_wsod()) +// $form['admin_settings']['autothumb'] = $autothumb; +// $form['admin_settings']['autoconv'] = $autoconv; + return $form; + } + + public function get_dimensions($video) { + return $this->transcoder->get_dimensions($video); + } + + public function create_job($video) { + return $this->transcoder->create_job($video); + } + + public function update_job($video) { + return $this->transcoder->update_job($video); + } + + public function delete_job($video) { + return $this->transcoder->delete_job($video); + } + + /** + * Load a file based on the file id ($fid) + * + * @param $fid + * Integer of the file id to be loaded. + */ + public function load_job($fid) { + return $this->transcoder->load_job($fid); + } + + public function load_job_queue() { + return $this->transcoder->load_job_queue(); + } + + public function load_completed_job(&$video) { + return $this->transcoder->load_completed_job($video); + } + +} + +interface transcoder_interface { + + public function create_job($video); + + public function update_job($video); + + public function delete_job($video); + + public function load_job($fid); + + public function load_job_queue(); + + public function load_completed_job(&$video); + + public function change_status($vid, $status); + + public function generate_thumbnails($video); + + public function convert_video($video); + + public function get_name(); + + public function get_value(); + + public function get_help(); + + public function admin_settings(); + + public function admin_settings_validate($form, &$form_state); +} \ No newline at end of file diff --git a/includes/video_helper.inc b/includes/video_helper.inc new file mode 100644 index 0000000..16598f9 --- /dev/null +++ b/includes/video_helper.inc @@ -0,0 +1,116 @@ +fid = $element['#item']['fid']; + $video->original = $element['#item']; + $extension = strtolower(pathinfo($element['#item']['filename'], PATHINFO_EXTENSION)); + $video->files->{$extension}->filename = pathinfo($element['#item']['filepath'], PATHINFO_FILENAME) . '.' . $extension; + $video->files->{$extension}->filepath = $element['#item']['filepath']; + $video->files->{$extension}->url = file_create_url($element['#item']['filepath']); + $video->files->{$extension}->extension = $extension; + $video->player = strtolower(pathinfo($element['#item']['filename'], PATHINFO_EXTENSION)); + $video->width = trim($dimensions[0]); + $video->height = trim($dimensions[1]); + $video->player_width = trim($player_dimensions[0]); + $video->player_height = trim($player_dimensions[1]); + $video->thumbnail = $this->thumbnail_object($element); + $video->formatter = $element['#formatter']; + $video->autoplay = variable_get('video_autoplay', TRUE); + $video->autobuffering = variable_get('video_autobuffering', TRUE); + $video->theora_player = variable_get('video_ogg_player', 'http://theora.org/cortado.jar'); + // lets find out if we have transcoded this file and update our paths. + if (isset($field['widget']['autoconversion']) && $field['widget']['autoconversion'] + && !$element['#item']['data']['bypass_autoconversion']) { + // discard all existing file data + $video->files = new stdClass(); + module_load_include('inc', 'video', '/includes/conversion'); + $conversion = new video_conversion; + $conversion->load_completed_job($video); + } +// echo '
';
+//    print_r($video);
+//    die();
+    // Let othere module to load the video files by referance
+    // Lets find out if we have pushed this file to the cdn if enabled.
+    // @TODO : add correct filesystem load to this
+    $filesystem = variable_get('vid_filesystem', 'drupal');
+    if ($filesystem != 'drupal' && !module_exists('video_zencoder')) {
+      module_load_include('inc', 'video', '/includes/filesystem');
+      $filesystem = new video_filesystem();
+      $filesystem->load_file($video);
+    }
+
+
+    // Moved to last to recheck incase we changed our extension above.
+    $video->flash_player = variable_get('video_extension_' . $video->player . '_flash_player', '');
+
+    // Return our object
+    return $video;
+  }
+
+  public function thumbnail_object($element) {
+    $field = content_fields($element['#field_name'], $element['#type_name']);
+    // Build our thumbnail object
+    $thumbnail = new stdClass();
+    $thumbnail->filepath = '';
+    $thumbnail->url = '';
+    //@todo future enhancements for our thumbnails
+    $thumbnail->alt = '';
+    $thumbnail->title = '';
+    $thumbnail->description = '';
+
+    // Setup our thumbnail path.
+    $use_default_img = isset($element['#item']['data']['use_default_video_thumb']) ? $element['#item']['data']['use_default_video_thumb'] : false;
+    if ($use_default_img && !empty($field['widget']['default_video_thumb']['filepath'])) {
+      $thumbnail->filepath = $field['widget']['default_video_thumb']['filepath'];
+    } elseif (isset($element['#item']['data']['video_thumb']) ? $element['#item']['data']['video_thumb'] : false) {
+      $thumbnail->filepath = $element['#item']['data']['video_thumb'];
+    } else {
+      //need some type of default if nothing is present
+      //drupal_set_message(t('No thumbnail has been configured for the video.'), 'error');
+    }
+    //lets check for an imagecache preset
+    if (isset($element['imagecache_preset'])) {
+      $thumbnail->url = imagecache_create_url($element['imagecache_preset'], $thumbnail->filepath);
+      $thumbnail->filepath = imagecache_create_path($element['imagecache_preset'], $thumbnail->filepath);
+    } else {
+      $thumbnail->url = file_create_url($thumbnail->filepath);
+    }
+
+    //swftools appends sites/default/files to the front of our path...
+    //@todo Is this a setting?  Need to figure this out.
+    $thumbnail->swfthumb = $thumbnail->filepath;
+    // Return our object
+    return $thumbnail;
+  }
+
+}
diff --git a/js/.cvsignore b/js/.cvsignore
new file mode 100644
index 0000000..e43b0f9
--- /dev/null
+++ b/js/.cvsignore
@@ -0,0 +1 @@
+.DS_Store
diff --git a/js/flowplayer-3.2.0.min.js b/js/flowplayer-3.2.0.min.js
new file mode 100644
index 0000000..2baa001
--- /dev/null
+++ b/js/flowplayer-3.2.0.min.js
@@ -0,0 +1,24 @@
+/* 
+ * flowplayer.js 3.2.0. The Flowplayer API
+ * 
+ * Copyright 2009 Flowplayer Oy
+ * 
+ * This file is part of Flowplayer.
+ * 
+ * Flowplayer is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * Flowplayer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Flowplayer.  If not, see .
+ * 
+ * Date: 2010-05-03 20:23:59 +0000 (Mon, 03 May 2010)
+ * Revision: 468 
+ */
+(function(){function g(o){console.log("$f.fireEvent",[].slice.call(o))}function k(q){if(!q||typeof q!="object"){return q}var o=new q.constructor();for(var p in q){if(q.hasOwnProperty(p)){o[p]=k(q[p])}}return o}function m(t,q){if(!t){return}var o,p=0,r=t.length;if(r===undefined){for(o in t){if(q.call(t[o],o,t[o])===false){break}}}else{for(var s=t[0];p1){var t=arguments[1],q=(arguments.length==3)?arguments[2]:{};if(typeof t=="string"){t={src:t}}t=i({bgcolor:"#000000",version:[9,0],expressInstall:"http://static.flowplayer.org/swf/expressinstall.swf",cachebusting:true},t);if(typeof o=="string"){if(o.indexOf(".")!=-1){var s=[];m(n(o),function(){s.push(new b(this,k(t),k(q)))});return new d(s)}else{var r=c(o);return new b(r!==null?r:o,t,q)}}else{if(o){return new b(o,t,q)}}}return null};i(window.$f,{fireEvent:function(){var o=[].slice.call(arguments);var q=$f(o[0]);return q?q._fireEvent(o.slice(1)):null},addPlugin:function(o,p){b.prototype[o]=p;return $f},each:m,extend:i});if(typeof jQuery=="function"){jQuery.fn.flowplayer=function(q,p){if(!arguments.length||typeof arguments[0]=="number"){var o=[];this.each(function(){var r=$f(this);if(r){o.push(r)}});return arguments.length?o[arguments[0]]:new d(o)}return this.each(function(){$f(this,k(q),p?k(p):{})})}}})();(function(){var h=document.all,j="http://www.adobe.com/go/getflashplayer",c=typeof jQuery=="function",e=/(\d+)[^\d]+(\d+)[^\d]*(\d*)/,b={width:"100%",height:"100%",id:"_"+(""+Math.random()).slice(9),allowfullscreen:true,allowscriptaccess:"always",quality:"high",version:[3,0],onFail:null,expressInstall:null,w3c:false,cachebusting:false};if(window.attachEvent){window.attachEvent("onbeforeunload",function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){}})}function i(l,f){if(f){for(key in f){if(f.hasOwnProperty(key)){l[key]=f[key]}}}return l}function a(f,n){var m=[];for(var l in f){if(f.hasOwnProperty(l)){m[l]=n(f[l])}}return m}window.flashembed=function(f,m,l){if(typeof f=="string"){f=document.getElementById(f.replace("#",""))}if(!f){return}if(typeof m=="string"){m={src:m}}return new d(f,i(i({},b),m),l)};var g=i(window.flashembed,{conf:b,getVersion:function(){var f;try{f=navigator.plugins["Shockwave Flash"].description.slice(16)}catch(n){try{var l=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");f=l&&l.GetVariable("$version")}catch(m){}}f=e.exec(f);return[f[1],f[3]]},asString:function(l){if(l===null||l===undefined){return null}var f=typeof l;if(f=="object"&&l.push){f="array"}switch(f){case"string":l=l.replace(new RegExp('(["\\\\])',"g"),"\\$1");l=l.replace(/^\s?(\d+\.?\d+)%/,"$1pct");return'"'+l+'"';case"array":return"["+a(l,function(o){return g.asString(o)}).join(",")+"]";case"function":return'"function()"';case"object":var m=[];for(var n in l){if(l.hasOwnProperty(n)){m.push('"'+n+'":'+g.asString(l[n]))}}return"{"+m.join(",")+"}"}return String(l).replace(/\s/g," ").replace(/\'/g,'"')},getHTML:function(o,l){o=i({},o);var n=''}o.width=o.height=o.id=o.w3c=o.src=null;o.onFail=o.version=o.expressInstall=null;for(var m in o){if(o[m]){n+=''}}var p="";if(l){for(var f in l){if(l[f]){var q=l[f];p+=f+"="+(/function|object/.test(typeof q)?g.asString(q):q)+"&"}}p=p.slice(0,-1);n+='"}n+="";return n},isSupported:function(f){return k[0]>f[0]||k[0]==f[0]&&k[1]>=f[1]}});var k=g.getVersion();function d(f,n,m){if(g.isSupported(n.version)){f.innerHTML=g.getHTML(n,m)}else{if(n.expressInstall&&g.isSupported([6,65])){f.innerHTML=g.getHTML(i(n,{src:n.expressInstall}),{MMredirectURL:location.href,MMplayerType:"PlugIn",MMdoctitle:document.title})}else{if(!f.innerHTML.replace(/\s/g,"")){f.innerHTML="

Flash version "+n.version+" or greater is required

"+(k[0]>0?"Your version is "+k:"You have no flash plugin installed")+"

"+(f.tagName=="A"?"

Click here to download latest version

":"

Download latest version from here

");if(f.tagName=="A"){f.onclick=function(){location.href=j}}}if(n.onFail){var l=n.onFail.call(this);if(typeof l=="string"){f.innerHTML=l}}}}if(h){window[n.id]=document.getElementById(n.id)}i(this,{getRoot:function(){return f},getOptions:function(){return n},getConf:function(){return m},getApi:function(){return f.firstChild}})}if(c){jQuery.tools=jQuery.tools||{version:"3.2.0"};jQuery.tools.flashembed={conf:b};jQuery.fn.flashembed=function(l,f){return this.each(function(){$(this).data("flashembed",flashembed(this,l,f))})}}})(); \ No newline at end of file diff --git a/js/jquery.media.js b/js/jquery.media.js new file mode 100644 index 0000000..dcffa3c --- /dev/null +++ b/js/jquery.media.js @@ -0,0 +1,458 @@ +/* + * jQuery Media Plugin for converting elements into rich media content. + * + * Examples and documentation at: http://malsup.com/jquery/media/ + * Copyright (c) 2007-2008 M. Alsup + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * @author: M. Alsup + * @version: 0.92 (24-SEP-2009) + * @requires jQuery v1.1.2 or later + * $Id$ + * + * Supported Media Players: + * - Flash + * - Quicktime + * - Real Player + * - Silverlight + * - Windows Media Player + * - iframe + * + * Supported Media Formats: + * Any types supported by the above players, such as: + * Video: asf, avi, flv, mov, mpg, mpeg, mp4, qt, smil, swf, wmv, 3g2, 3gp + * Audio: aif, aac, au, gsm, mid, midi, mov, mp3, m4a, snd, rm, wav, wma + * Other: bmp, html, pdf, psd, qif, qtif, qti, tif, tiff, xaml + * + * Thanks to Mark Hicken and Brent Pedersen for helping me debug this on the Mac! + * Thanks to Dan Rossi for numerous bug reports and code bits! + * Thanks to Skye Giordano for several great suggestions! + * Thanks to Richard Connamacher for excellent improvements to the non-IE behavior! + */ +;(function($) { + +/** + * Chainable method for converting elements into rich media. + * + * @param options + * @param callback fn invoked for each matched element before conversion + * @param callback fn invoked for each matched element after conversion + */ +$.fn.media = function(options, f1, f2) { + if (options == 'undo') { + return this.each(function() { + var $this = $(this); + var html = $this.data('media.origHTML'); + if (html) + $this.replaceWith(html); + }); + } + + return this.each(function() { + if (typeof options == 'function') { + f2 = f1; + f1 = options; + options = {}; + } + var o = getSettings(this, options); + // pre-conversion callback, passes original element and fully populated options + if (typeof f1 == 'function') f1(this, o); + + var r = getTypesRegExp(); + var m = r.exec(o.src.toLowerCase()) || ['']; + + o.type ? m[0] = o.type : m.shift(); + for (var i=0; i < m.length; i++) { + fn = m[i].toLowerCase(); + if (isDigit(fn[0])) fn = 'fn' + fn; // fns can't begin with numbers + if (!$.fn.media[fn]) + continue; // unrecognized media type + // normalize autoplay settings + var player = $.fn.media[fn+'_player']; + if (!o.params) o.params = {}; + if (player) { + var num = player.autoplayAttr == 'autostart'; + o.params[player.autoplayAttr || 'autoplay'] = num ? (o.autoplay ? 1 : 0) : o.autoplay ? true : false; + } + var $div = $.fn.media[fn](this, o); + + $div.css('backgroundColor', o.bgColor).width(o.width); + + if (o.canUndo) { + var $temp = $('
').append(this); + $div.data('media.origHTML', $temp.html()); // store original markup + } + + // post-conversion callback, passes original element, new div element and fully populated options + if (typeof f2 == 'function') f2(this, $div[0], o, player.name); + break; + } + }); +}; + +/** + * Non-chainable method for adding or changing file format / player mapping + * @name mapFormat + * @param String format File format extension (ie: mov, wav, mp3) + * @param String player Player name to use for the format (one of: flash, quicktime, realplayer, winmedia, silverlight or iframe + */ +$.fn.media.mapFormat = function(format, player) { + if (!format || !player || !$.fn.media.defaults.players[player]) return; // invalid + format = format.toLowerCase(); + if (isDigit(format[0])) format = 'fn' + format; + $.fn.media[format] = $.fn.media[player]; + $.fn.media[format+'_player'] = $.fn.media.defaults.players[player]; +}; + +// global defautls; override as needed +$.fn.media.defaults = { + standards: false, // use object tags only (no embeds for non-IE browsers) + canUndo: true, // tells plugin to store the original markup so it can be reverted via: $(sel).mediaUndo() + width: 400, + height: 400, + autoplay: 0, // normalized cross-player setting + bgColor: '#ffffff', // background color + params: { wmode: 'transparent'}, // added to object element as param elements; added to embed element as attrs + attrs: {}, // added to object and embed elements as attrs + flvKeyName: 'file', // key used for object src param (thanks to Andrea Ercolino) + flashvars: {}, // added to flash content as flashvars param/attr + flashVersion: '7', // required flash version + expressInstaller: null, // src for express installer + + // default flash video and mp3 player (@see: http://jeroenwijering.com/?item=Flash_Media_Player) + flvPlayer: 'mediaplayer.swf', + mp3Player: 'mediaplayer.swf', + + // @see http://msdn2.microsoft.com/en-us/library/bb412401.aspx + silverlight: { + inplaceInstallPrompt: 'true', // display in-place install prompt? + isWindowless: 'true', // windowless mode (false for wrapping markup) + framerate: '24', // maximum framerate + version: '0.9', // Silverlight version + onError: null, // onError callback + onLoad: null, // onLoad callback + initParams: null, // object init params + userContext: null // callback arg passed to the load callback + } +}; + +// Media Players; think twice before overriding +$.fn.media.defaults.players = { + flash: { + name: 'flash', + title: 'Flash', + types: 'flv,mp3,swf', + mimetype: 'application/x-shockwave-flash', + pluginspage: 'http://www.adobe.com/go/getflashplayer', + ieAttrs: { + classid: 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000', + type: 'application/x-oleobject', + codebase: 'http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=' + $.fn.media.defaults.flashVersion + } + }, + quicktime: { + name: 'quicktime', + title: 'QuickTime', + mimetype: 'video/quicktime', + pluginspage: 'http://www.apple.com/quicktime/download/', + types: 'aif,aiff,aac,au,bmp,gsm,mov,mid,midi,mpg,mpeg,mp4,m4a,psd,qt,qtif,qif,qti,snd,tif,tiff,wav,3g2,3gp', + ieAttrs: { + classid: 'clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B', + codebase: 'http://www.apple.com/qtactivex/qtplugin.cab' + } + }, + realplayer: { + name: 'real', + title: 'RealPlayer', + types: 'ra,ram,rm,rpm,rv,smi,smil', + mimetype: 'audio/x-pn-realaudio-plugin', + pluginspage: 'http://www.real.com/player/', + autoplayAttr: 'autostart', + ieAttrs: { + classid: 'clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA' + } + }, + winmedia: { + name: 'winmedia', + title: 'Windows Media', + types: 'asx,asf,avi,wma,wmv', + mimetype: $.browser.mozilla && isFirefoxWMPPluginInstalled() ? 'application/x-ms-wmp' : 'application/x-mplayer2', + pluginspage: 'http://www.microsoft.com/Windows/MediaPlayer/', + autoplayAttr: 'autostart', + oUrl: 'url', + ieAttrs: { + classid: 'clsid:6BF52A52-394A-11d3-B153-00C04F79FAA6', + type: 'application/x-oleobject' + } + }, + // special cases + iframe: { + name: 'iframe', + types: 'html,pdf' + }, + silverlight: { + name: 'silverlight', + types: 'xaml' + } +}; + +// +// everything below here is private +// + + +// detection script for FF WMP plugin (http://www.therossman.org/experiments/wmp_play.html) +// (hat tip to Mark Ross for this script) +function isFirefoxWMPPluginInstalled() { + var plugs = navigator.plugins; + for (var i = 0; i < plugs.length; i++) { + var plugin = plugs[i]; + if (plugin['filename'] == 'np-mswmp.dll') + return true; + } + return false; +} + +var counter = 1; + +for (var player in $.fn.media.defaults.players) { + var types = $.fn.media.defaults.players[player].types; + $.each(types.split(','), function(i,o) { + if (isDigit(o[0])) o = 'fn' + o; + $.fn.media[o] = $.fn.media[player] = getGenerator(player); + $.fn.media[o+'_player'] = $.fn.media.defaults.players[player]; + }); +}; + +function getTypesRegExp() { + var types = ''; + for (var player in $.fn.media.defaults.players) { + if (types.length) types += ','; + types += $.fn.media.defaults.players[player].types; + }; + return new RegExp('\\.(' + types.replace(/,/ig,'|') + ')\\b$'); +}; + +function getGenerator(player) { + return function(el, options) { + return generate(el, options, player); + }; +}; + +function isDigit(c) { + return '0123456789'.indexOf(c) > -1; +}; + +// flatten all possible options: global defaults, meta, option obj +function getSettings(el, options) { + options = options || {}; + var $el = $(el); + var cls = el.className || ''; + // support metadata plugin (v1.0 and v2.0) + var meta = $.metadata ? $el.metadata() : $.meta ? $el.data() : {}; + meta = meta || {}; + var w = meta.width || parseInt(((cls.match(/w:(\d+)/)||[])[1]||0)); + var h = meta.height || parseInt(((cls.match(/h:(\d+)/)||[])[1]||0)); + + if (w) meta.width = w; + if (h) meta.height = h; + if (cls) meta.cls = cls; + + var a = $.fn.media.defaults; + var b = options; + var c = meta; + + var p = { params: { bgColor: options.bgColor || $.fn.media.defaults.bgColor } }; + var opts = $.extend({}, a, b, c); + $.each(['attrs','params','flashvars','silverlight'], function(i,o) { + opts[o] = $.extend({}, p[o] || {}, a[o] || {}, b[o] || {}, c[o] || {}); + }); + + if (typeof opts.caption == 'undefined') opts.caption = $el.text(); + + // make sure we have a source! + opts.src = opts.src || $el.attr('href') || $el.attr('src') || 'unknown'; + return opts; +}; + +// +// Flash Player +// + +// generate flash using SWFObject library if possible +$.fn.media.swf = function(el, opts) { + if (!window.SWFObject && !window.swfobject) { + // roll our own + if (opts.flashvars) { + var a = []; + for (var f in opts.flashvars) + a.push(f + '=' + opts.flashvars[f]); + if (!opts.params) opts.params = {}; + opts.params.flashvars = a.join('&'); + } + return generate(el, opts, 'flash'); + } + + var id = el.id ? (' id="'+el.id+'"') : ''; + var cls = opts.cls ? (' class="' + opts.cls + '"') : ''; + var $div = $(''); + + // swfobject v2+ + if (window.swfobject) { + $(el).after($div).appendTo($div); + if (!el.id) el.id = 'movie_player_' + counter++; + + // replace el with swfobject content + swfobject.embedSWF(opts.src, el.id, opts.width, opts.height, opts.flashVersion, + opts.expressInstaller, opts.flashvars, opts.params, opts.attrs); + } + // swfobject < v2 + else { + $(el).after($div).remove(); + var so = new SWFObject(opts.src, 'movie_player_' + counter++, opts.width, opts.height, opts.flashVersion, opts.bgColor); + if (opts.expressInstaller) so.useExpressInstall(opts.expressInstaller); + + for (var p in opts.params) + if (p != 'bgColor') so.addParam(p, opts.params[p]); + for (var f in opts.flashvars) + so.addVariable(f, opts.flashvars[f]); + so.write($div[0]); + } + + if (opts.caption) $('
').appendTo($div).html(opts.caption); + return $div; +}; + +// map flv and mp3 files to the swf player by default +$.fn.media.flv = $.fn.media.mp3 = function(el, opts) { + var src = opts.src; + var player = /\.mp3\b/i.test(src) ? $.fn.media.defaults.mp3Player : $.fn.media.defaults.flvPlayer; + var key = opts.flvKeyName; + src = encodeURIComponent(src); + opts.src = player; + opts.src = opts.src + '?'+key+'=' + (src); + var srcObj = {}; + srcObj[key] = src; + opts.flashvars = $.extend({}, srcObj, opts.flashvars ); + return $.fn.media.swf(el, opts); +}; + +// +// Silverlight +// +$.fn.media.xaml = function(el, opts) { + if (!window.Sys || !window.Sys.Silverlight) { + if ($.fn.media.xaml.warning) return; + $.fn.media.xaml.warning = 1; + alert('You must include the Silverlight.js script.'); + return; + } + + var props = { + width: opts.width, + height: opts.height, + background: opts.bgColor, + inplaceInstallPrompt: opts.silverlight.inplaceInstallPrompt, + isWindowless: opts.silverlight.isWindowless, + framerate: opts.silverlight.framerate, + version: opts.silverlight.version + }; + var events = { + onError: opts.silverlight.onError, + onLoad: opts.silverlight.onLoad + }; + + var id1 = el.id ? (' id="'+el.id+'"') : ''; + var id2 = opts.id || 'AG' + counter++; + // convert element to div + var cls = opts.cls ? (' class="' + opts.cls + '"') : ''; + var $div = $(''); + $(el).after($div).remove(); + + Sys.Silverlight.createObjectEx({ + source: opts.src, + initParams: opts.silverlight.initParams, + userContext: opts.silverlight.userContext, + id: id2, + parentElement: $div[0], + properties: props, + events: events + }); + + if (opts.caption) $('
').appendTo($div).html(opts.caption); + return $div; +}; + +// +// generate object/embed markup +// +function generate(el, opts, player) { + var $el = $(el); + var o = $.fn.media.defaults.players[player]; + + if (player == 'iframe') { + var o = $(''); + o.attr('src', opts.src); + o.css('backgroundColor', o.bgColor); + } + else if ($.browser.msie) { + var a = [''); + var p = ['']; + for (var key in opts.params) + p.push(''); + var o = document.createElement(a.join('')); + for (var i=0; i < p.length; i++) + o.appendChild(document.createElement(p[i])); + } + else if (o.standards) { + // Rewritten to be standards compliant by Richard Connamacher + var a = [''); + a.push(''); + for (var key in opts.params) { + if (key == 'wmode' && player != 'flash') // FF3/Quicktime borks on wmode + continue; + a.push(''); + } + // Alternate HTML + a.push('

'+o.title+' Required

'+o.title+' is required to view this media. Download Here.

'); + a.push(''); + } + else { + var a = [''); + } + // convert element to div + var id = el.id ? (' id="'+el.id+'"') : ''; + var cls = opts.cls ? (' class="' + opts.cls + '"') : ''; + var $div = $(''); + $el.after($div).remove(); + ($.browser.msie || player == 'iframe') ? $div.append(o) : $div.html(a.join('')); + if (opts.caption) $('
').appendTo($div).html(opts.caption); + return $div; +}; + + +})(jQuery); diff --git a/js/jquery.metadata.js b/js/jquery.metadata.js new file mode 100644 index 0000000..e011f52 --- /dev/null +++ b/js/jquery.metadata.js @@ -0,0 +1,148 @@ +/* + * Metadata - jQuery plugin for parsing metadata from elements + * + * Copyright (c) 2006 John Resig, Yehuda Katz, J�örn Zaefferer, Paul McLanahan + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Revision: $Id$ + * + */ + +/** + * Sets the type of metadata to use. Metadata is encoded in JSON, and each property + * in the JSON will become a property of the element itself. + * + * There are four supported types of metadata storage: + * + * attr: Inside an attribute. The name parameter indicates *which* attribute. + * + * class: Inside the class attribute, wrapped in curly braces: { } + * + * elem: Inside a child element (e.g. a script tag). The + * name parameter indicates *which* element. + * html5: Values are stored in data-* attributes. + * + * The metadata for an element is loaded the first time the element is accessed via jQuery. + * + * As a result, you can define the metadata type, use $(expr) to load the metadata into the elements + * matched by expr, then redefine the metadata type and run another $(expr) for other elements. + * + * @name $.metadata.setType + * + * @example

This is a p

+ * @before $.metadata.setType("class") + * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label" + * @desc Reads metadata from the class attribute + * + * @example

This is a p

+ * @before $.metadata.setType("attr", "data") + * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label" + * @desc Reads metadata from a "data" attribute + * + * @example

This is a p

+ * @before $.metadata.setType("elem", "script") + * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label" + * @desc Reads metadata from a nested script element + * + * @example

This is a p

+ * @before $.metadata.setType("html5") + * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label" + * @desc Reads metadata from a series of data-* attributes + * + * @param String type The encoding type + * @param String name The name of the attribute to be used to get metadata (optional) + * @cat Plugins/Metadata + * @descr Sets the type of encoding to be used when loading metadata for the first time + * @type undefined + * @see metadata() + */ + +(function($) { + +$.extend({ + metadata : { + defaults : { + type: 'class', + name: 'metadata', + cre: /({.*})/, + single: 'metadata' + }, + setType: function( type, name ){ + this.defaults.type = type; + this.defaults.name = name; + }, + get: function( elem, opts ){ + var settings = $.extend({},this.defaults,opts); + // check for empty string in single property + if ( !settings.single.length ) settings.single = 'metadata'; + + var data = $.data(elem, settings.single); + // returned cached data if it already exists + if ( data ) return data; + + data = "{}"; + + var getData = function(data) { + if(typeof data != "string") return data; + + if( data.indexOf('{') < 0 ) { + data = eval("(" + data + ")"); + } + } + + var getObject = function(data) { + if(typeof data != "string") return data; + + data = eval("(" + data + ")"); + return data; + } + + if ( settings.type == "html5" ) { + var object = {}; + $( elem.attributes ).each(function() { + var name = this.nodeName; + if(name.match(/^data-/)) name = name.replace(/^data-/, ''); + else return true; + object[name] = getObject(this.nodeValue); + }); + } else { + if ( settings.type == "class" ) { + var m = settings.cre.exec( elem.className ); + if ( m ) + data = m[1]; + } else if ( settings.type == "elem" ) { + if( !elem.getElementsByTagName ) return; + var e = elem.getElementsByTagName(settings.name); + if ( e.length ) + data = $.trim(e[0].innerHTML); + } else if ( elem.getAttribute != undefined ) { + var attr = elem.getAttribute( settings.name ); + if ( attr ) + data = attr; + } + object = getObject(data.indexOf("{") < 0 ? "{" + data + "}" : data); + } + + $.data( elem, settings.single, object ); + return object; + } + } +}); + +/** + * Returns the metadata object for the first member of the jQuery object. + * + * @name metadata + * @descr Returns element's metadata object + * @param Object opts An object contianing settings to override the defaults + * @type jQuery + * @cat Plugins/Metadata + */ +$.fn.metadata = function( opts ){ + return $.metadata.get( this[0], opts ); +}; + +})(jQuery); \ No newline at end of file diff --git a/js/video.js b/js/video.js new file mode 100644 index 0000000..015724f --- /dev/null +++ b/js/video.js @@ -0,0 +1,122 @@ +// $Id$ + +/** + * @file + * Adds some show/hide to the admin form to make the UXP easier. + * + */ + +$(document).ready(function() { + //lets see if we have any jmedia movies + if($.fn.media) { + $('.jmedia').media(); + } + + video_hide_all_options(); + $("input[name='vid_convertor']").change(function() { + video_hide_all_options(); + }); + + // change metadata options + video_hide_all__metadata_options(); + $("input[name='vid_metadata']").change(function() { + video_hide_all__metadata_options(); + }); + + // change metadata options + video_hide_all__filesystem_options(); + $("input[name='vid_filesystem']").change(function() { + video_hide_all__filesystem_options(); + }); + + $('.video_select').each(function() { + var ext = $(this).attr('rel'); + $('select', this).change(function() { + if($(this).val() == 'video_play_flv') { + $('#flv_player_'+ext).show(); + } else { + $('#flv_player_'+ext).hide(); + } + }); + if($('select', this).val() == 'video_play_flv') { + $('#flv_player_'+ext).show(); + } + }); + + if(Drupal.settings.video) { + $.fn.media.defaults.flvPlayer = Drupal.settings.video.flvplayer; + + } + + //lets setup our colorbox videos + $('.video-box').each(function() { + var url = $(this).attr('href'); + var data = $(this).metadata(); + var width = data.width; + var height= data.height; + var player = Drupal.settings.video.player; //player can be either jwplayer or flowplayer. + $(this).colorbox({ + html: '', + onComplete:function() { + if(player == 'flowplayer') { + flowplayer("video-overlay", Drupal.settings.video.flvplayer, { + clip: { + autoPlay: Drupal.settings.video.autoplay, + autoBuffering: Drupal.settings.video.autobuffer + } + }); + } else { + $('#video-overlay').media({ + flashvars: { + autostart: Drupal.settings.video.autoplay + }, + width:width, + height:height + }); + } + } + }); + }); +}); + +function video_hide_all_options() { + $("input[name='vid_convertor']").each(function() { + var id = $(this).val(); + $('#'+id).hide(); + if ($(this).is(':checked')) { + $('#' + id).show(); + } + }); +} + +function videoftp_thumbnail_change() { + // Add handlers for the video thumbnail radio buttons to update the large thumbnail onchange. + $(".video-thumbnails input").each(function() { + var path = $(this).val(); + if($(this).is(':checked')) { + var holder = $(this).attr('rel'); + $('.'+holder+' img').attr('src', Drupal.settings.basePath + path); + } + }); + +} + +function video_hide_all__metadata_options() { + $("input[name='vid_metadata']").each(function() { + var id = $(this).val(); + $('#'+id).hide(); + if ($(this).is(':checked')) { + $('#' + id).show(); + } + }); +} + +function video_hide_all__filesystem_options() { + $("input[name='vid_filesystem']").each(function() { + var id = $(this).val(); + $('#'+id).hide(); + if ($(this).is(':checked')) { + $('#' + id).show(); + } + }); +} diff --git a/metadata/.cvsignore b/metadata/.cvsignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/metadata/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/metadata/flvtool2.inc b/metadata/flvtool2.inc new file mode 100644 index 0000000..2065ea2 --- /dev/null +++ b/metadata/flvtool2.inc @@ -0,0 +1,86 @@ +params['cmd_path'] = variable_get('video_metadata_path', $this->meta_command_path); + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/metadata_interface#get_name() + */ + public function get_name() { + return $this->name; + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/metadata_interface#get_help() + */ + public function get_help() { + return t('!flvtools calculates various meta data and inserts a onMetaData tag in the video. It cuts FLV files and adds cue Points (onCuePoint). A debug command shows the inside of a FLV file and the print command gives meta data information in XML or YAML format.', array('!flvtools' => l(t('FlvTools2 '), 'http://github.com/unnu/flvtool2'))); + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/metadata_interface#get_value() + */ + public function get_value() { + return $this->value; + } + + public function run_command($options) { + $command = $this->nice . ' ' . $this->params['cmd_path'] . ' ' . $options . ' 2>&1'; + watchdog('video_metadata', 'Executing command: ' . $command, array(), WATCHDOG_DEBUG); + ob_start(); + passthru($command, $command_return); + $output = ob_get_contents(); + ob_end_clean(); + return $output; + } + + public function process($video) { + $command_output = $this->run_command($this->meta_command . ' ' . $video); + return $command_output; + } + + public function admin_settings() { + $form = array(); + $form['video_flvtool2_start'] = array( + '#type' => 'markup', + '#value' => '
', + ); + $form['video_metadata_path'] = array( + '#type' => 'textfield', + '#title' => t('Path to FLVTool2'), + '#description' => t('Executable path to flvtool2.'), + '#default_value' => variable_get('video_metadata_path', $this->meta_command_path), + ); + $form['video_flvtool2_end'] = array( + '#type' => 'markup', + '#value' => '
', + ); + return $form; + } + + public function admin_settings_validate($form, $form_state) { + return; + } + +} + +?> diff --git a/plugins/.cvsignore b/plugins/.cvsignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/plugins/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/plugins/video_s3/filesystem/video_s3.inc b/plugins/video_s3/filesystem/video_s3.inc new file mode 100644 index 0000000..e103e1c --- /dev/null +++ b/plugins/video_s3/filesystem/video_s3.inc @@ -0,0 +1,183 @@ +name; + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/filesystem_interface#get_help() + */ + public function get_help() { + $help = t('Amazon Simple Storage Service (!s3) to store your video files. This free\'s up bandwidth from your site, providing a faster experience for your users. Simply enable this and enter your authentication details and your done! ', array('!s3' => l(t('Aamzon S3'), 'http://s3.amazonaws.com'))); + return $help; + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/filesystem_interface#get_value() + */ + public function get_value() { + return $this->value; + } + + public function run_command($options) { + return; + } + + public function admin_settings() { + $form = array(); + $form['amazon_s3_ssl'] = array( + '#type' => 'checkbox', + '#title' => t('Enable SSL?'), + '#default_value' => variable_get('amazon_s3_ssl', FALSE), + '#description' => t('If you would like to use ssl when transfering your files enable this option.'), + ); + $form['amazon_s3_private'] = array( + '#type' => 'checkbox', + '#title' => t('Enable Private?'), + '#default_value' => variable_get('amazon_s3_private', FALSE), + '#description' => t('If you would like to use private transfering for your files enable this option.'), + ); + $form['amazon_s3_lifetime'] = array( + '#type' => 'textfield', + '#title' => t('Private Url Lifetime'), + '#default_value' => variable_get('amazon_s3_lifetime', '1800'), + '#size' => 5, + ); + + + $form['amazon_s3_access_key'] = array( + '#type' => 'textfield', + '#title' => t('Access Key ID'), + '#default_value' => variable_get('amazon_s3_access_key', ''), + '#size' => 50, + ); + $form['amazon_s3_secret_access_key'] = array( + '#type' => 'password', + '#title' => t('Secret Access Key'), + '#default_value' => variable_get('amazon_s3_secret_access_key', ''), + '#description' => t('Once saved, you do not need to re-enter your secret key. If you need to update your key, then fill this out to update it.'), + '#size' => 50, + ); + //@todo Maybe move this to the admin settings page instead of global? + $form['amazon_s3_bucket'] = array( + '#type' => 'textfield', + '#title' => t('Bucket'), + '#description' => t('Enter the bucket you wish to store your videos in. If the bucket doesn\'t exist the system will attempt to create it.'), + '#default_value' => variable_get('amazon_s3_bucket', ''), + '#size' => 50, + ); + + //lets show our buckets in table format with a delete link. + //@todo add permissions + //were enabled, that means they have successfully connected and created a bucket. + if (variable_get('amazon_s3_access_key', false) && variable_get('vid_filesystem', 'drupal') == 'video_s3') { + module_load_include('inc', 'video_s3', '/includes/amazon_s3'); + $s3 = new video_amazon_s3; + $s3->connect(); + $buckets = $s3->s3->listBuckets(); + // Setup our header. + $header = array(t('Bucket Name'), t('Total Objects'), t('Actions')); + $rows = array(); + foreach ($buckets as $bucket) { + $objects = count($s3->s3->getBucket($bucket)); + $actions = l(t('Delete'), 'admin/settings/video/amazon_s3/bucket/' . $bucket . '/delete'); + $rows[] = array($bucket, $objects, $actions); + } + $form['amazon_info'] = array( + '#type' => 'fieldset', + '#title' => t('Amazon S3 Information'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['amazon_info']['buckets'] = array( + '#type' => 'markup', + '#value' => theme('table', $header, $rows), + ); + } + return $form; + } + + public function admin_settings_validate(&$form, &$form_state) { + // Check for CURL + if (!extension_loaded('curl') && !@dl(PHP_SHLIB_SUFFIX == 'so' ? 'curl.so' : 'php_curl.dll')) { + form_set_error('amazon_s3', t('The CURL extension is not loaded.')); + } else { + $bucket = $form_state['values']['amazon_s3_bucket']; + // S3 buckets must contain only lower case alphanumeric characters, dots and dashes. + if (!preg_match("/^[a-z.-]+$/", $bucket)) { + form_set_error('amazon_s3_bucket', t('S3 buckets must contain only lower case alphanumeric characters, dots and dashes.')); + } else { + $access_key = $form_state['values']['amazon_s3_access_key']; + // check our secret key. + if (!empty($form_state['values']['amazon_s3_secret_access_key'])) { + $secret_key = $form_state['values']['amazon_s3_secret_access_key']; + } else { + // Add our secret key back in to persist its value. + $form_state['values']['amazon_s3_secret_access_key'] = variable_get('amazon_s3_secret_access_key', ''); + $secret_key = variable_get('amazon_s3_secret_access_key', ''); + } + $ssl = isset($form_state['values']['amazon_s3_ssl']) && $form_state['values']['amazon_s3_ssl'] ? TRUE : FALSE; + // Lets verify our credentials and verify our bucket exists, if not attempt to create it. + module_load_include('inc', 'video_s3', '/includes/amazon_s3'); + $s3 = new video_amazon_s3; + $s3->connect($access_key, $secret_key, $ssl); + $buckets = $s3->s3->listBuckets(); + if (!$buckets || !(in_array($bucket, $buckets))) { + // Create a bucket with public read access + if ($s3->s3->putBucket($bucket, S3::ACL_PUBLIC_READ)) { + // set access control policy to zencoder if module is enabled + // @TODO : Add this to video_zencoder module + if (module_exists('video_zencoder')) { + $acp['acl'][] = array( + 'type' => 'AmazonCustomerByEmail', + 'email' => 'aws@zencoder.com', + 'permission' => 'WRITE' + ); + $s3->s3->setAccessControlPolicy($bucket, '', $acp); + } + drupal_set_message(t('Successfully created the bucket %bucket.', array('%bucket' => $bucket))); + } else { + form_set_error('amazon_s3_bucket', t('Could not verify or create the bucket %bucket.', array('%bucket' => $bucket))); + } + } + } + } + } + +} + +?> diff --git a/plugins/video_s3/includes/S3.php b/plugins/video_s3/includes/S3.php new file mode 100644 index 0000000..70d3ad8 --- /dev/null +++ b/plugins/video_s3/includes/S3.php @@ -0,0 +1,1365 @@ +getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + $results = array(); + if (!isset($rest->body->Buckets)) return $results; + + if ($detailed) { + if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName)) + $results['owner'] = array( + 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID + ); + $results['buckets'] = array(); + foreach ($rest->body->Buckets->Bucket as $b) + $results['buckets'][] = array( + 'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate) + ); + } else + foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name; + + return $results; + } + + + /* + * Get contents for a bucket + * + * If maxKeys is null this method will loop through truncated result sets + * + * @param string $bucket Bucket name + * @param string $prefix Prefix + * @param string $marker Marker (last file listed) + * @param string $maxKeys Max keys (maximum number of keys to return) + * @param string $delimiter Delimiter + * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes + * @return array | false + */ + public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false) { + $rest = new S3Request('GET', $bucket, ''); + if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix); + if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker); + if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys); + if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter); + $response = $rest->getResponse(); + if ($response->error === false && $response->code !== 200) + $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status'); + if ($response->error !== false) { + trigger_error(sprintf("S3::getBucket(): [%s] %s", $response->error['code'], $response->error['message']), E_USER_WARNING); + return false; + } + + $results = array(); + + $nextMarker = null; + if (isset($response->body, $response->body->Contents)) + foreach ($response->body->Contents as $c) { + $results[(string)$c->Key] = array( + 'name' => (string)$c->Key, + 'time' => strtotime((string)$c->LastModified), + 'size' => (int)$c->Size, + 'hash' => substr((string)$c->ETag, 1, -1) + ); + $nextMarker = (string)$c->Key; + } + + if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes)) + foreach ($response->body->CommonPrefixes as $c) + $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix); + + if (isset($response->body, $response->body->IsTruncated) && + (string)$response->body->IsTruncated == 'false') return $results; + + if (isset($response->body, $response->body->NextMarker)) + $nextMarker = (string)$response->body->NextMarker; + + // Loop through truncated results if maxKeys isn't specified + if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true') + do { + $rest = new S3Request('GET', $bucket, ''); + if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix); + $rest->setParameter('marker', $nextMarker); + if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter); + + if (($response = $rest->getResponse(true)) == false || $response->code !== 200) break; + + if (isset($response->body, $response->body->Contents)) + foreach ($response->body->Contents as $c) { + $results[(string)$c->Key] = array( + 'name' => (string)$c->Key, + 'time' => strtotime((string)$c->LastModified), + 'size' => (int)$c->Size, + 'hash' => substr((string)$c->ETag, 1, -1) + ); + $nextMarker = (string)$c->Key; + } + + if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes)) + foreach ($response->body->CommonPrefixes as $c) + $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix); + + if (isset($response->body, $response->body->NextMarker)) + $nextMarker = (string)$response->body->NextMarker; + + } while ($response !== false && (string)$response->body->IsTruncated == 'true'); + + return $results; + } + + + /** + * Put a bucket + * + * @param string $bucket Bucket name + * @param constant $acl ACL flag + * @param string $location Set as "EU" to create buckets hosted in Europe + * @return boolean + */ + public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false) { + $rest = new S3Request('PUT', $bucket, ''); + $rest->setAmzHeader('x-amz-acl', $acl); + + if ($location !== false) { + $dom = new DOMDocument; + $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration'); + $locationConstraint = $dom->createElement('LocationConstraint', strtoupper($location)); + $createBucketConfiguration->appendChild($locationConstraint); + $dom->appendChild($createBucketConfiguration); + $rest->data = $dom->saveXML(); + $rest->size = strlen($rest->data); + $rest->setHeader('Content-Type', 'application/xml'); + } + $rest = $rest->getResponse(); + + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Delete an empty bucket + * + * @param string $bucket Bucket name + * @return boolean + */ + public static function deleteBucket($bucket) { + $rest = new S3Request('DELETE', $bucket); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 204) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::deleteBucket({$bucket}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Create input info array for putObject() + * + * @param string $file Input file + * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own) + * @return array | false + */ + public static function inputFile($file, $md5sum = true) { + if (!file_exists($file) || !is_file($file) || !is_readable($file)) { + trigger_error('S3::inputFile(): Unable to open input file: '.$file, E_USER_WARNING); + return false; + } + return array('file' => $file, 'size' => filesize($file), + 'md5sum' => $md5sum !== false ? (is_string($md5sum) ? $md5sum : + base64_encode(md5_file($file, true))) : ''); + } + + + /** + * Create input array info for putObject() with a resource + * + * @param string $resource Input resource to read from + * @param integer $bufferSize Input byte size + * @param string $md5sum MD5 hash to send (optional) + * @return array | false + */ + public static function inputResource(&$resource, $bufferSize, $md5sum = '') { + if (!is_resource($resource) || $bufferSize < 0) { + trigger_error('S3::inputResource(): Invalid resource or buffer size', E_USER_WARNING); + return false; + } + $input = array('size' => $bufferSize, 'md5sum' => $md5sum); + $input['fp'] =& $resource; + return $input; + } + + + /** + * Put an object + * + * @param mixed $input Input data + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param constant $acl ACL constant + * @param array $metaHeaders Array of x-amz-meta-* headers + * @param array $requestHeaders Array of request headers or content type as a string + * @return boolean + */ + public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array()) { + if ($input === false) return false; + $rest = new S3Request('PUT', $bucket, $uri); + + if (is_string($input)) $input = array( + 'data' => $input, 'size' => strlen($input), + 'md5sum' => base64_encode(md5($input, true)) + ); + + // Data + if (isset($input['fp'])) + $rest->fp =& $input['fp']; + elseif (isset($input['file'])) + $rest->fp = @fopen($input['file'], 'rb'); + elseif (isset($input['data'])) + $rest->data = $input['data']; + + // Content-Length (required) + if (isset($input['size']) && $input['size'] >= 0) + $rest->size = $input['size']; + else { + if (isset($input['file'])) + $rest->size = filesize($input['file']); + elseif (isset($input['data'])) + $rest->size = strlen($input['data']); + } + + // Custom request headers (Content-Type, Content-Disposition, Content-Encoding) + if (is_array($requestHeaders)) + foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v); + elseif (is_string($requestHeaders)) // Support for legacy contentType parameter + $input['type'] = $requestHeaders; + + // Content-Type + if (!isset($input['type'])) { + if (isset($requestHeaders['Content-Type'])) + $input['type'] =& $requestHeaders['Content-Type']; + elseif (isset($input['file'])) + $input['type'] = self::__getMimeType($input['file']); + else + $input['type'] = 'application/octet-stream'; + } + + // We need to post with Content-Length and Content-Type, MD5 is optional + if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false)) { + $rest->setHeader('Content-Type', $input['type']); + if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']); + + $rest->setAmzHeader('x-amz-acl', $acl); + foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v); + $rest->getResponse(); + } else + $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters'); + + if ($rest->response->error === false && $rest->response->code !== 200) + $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status'); + if ($rest->response->error !== false) { + trigger_error(sprintf("S3::putObject(): [%s] %s", $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Put an object from a file (legacy function) + * + * @param string $file Input file path + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param constant $acl ACL constant + * @param array $metaHeaders Array of x-amz-meta-* headers + * @param string $contentType Content type + * @return boolean + */ + public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null) { + return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType); + } + + + /** + * Put an object from a string (legacy function) + * + * @param string $string Input data + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param constant $acl ACL constant + * @param array $metaHeaders Array of x-amz-meta-* headers + * @param string $contentType Content type + * @return boolean + */ + public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain') { + return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType); + } + + + /** + * Get an object + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param mixed $saveTo Filename or resource to write to + * @return mixed + */ + public static function getObject($bucket, $uri, $saveTo = false) { + $rest = new S3Request('GET', $bucket, $uri); + if ($saveTo !== false) { + if (is_resource($saveTo)) + $rest->fp =& $saveTo; + else + if (($rest->fp = @fopen($saveTo, 'wb')) !== false) + $rest->file = realpath($saveTo); + else + $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo); + } + if ($rest->response->error === false) $rest->getResponse(); + + if ($rest->response->error === false && $rest->response->code !== 200) + $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status'); + if ($rest->response->error !== false) { + trigger_error(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s", + $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING); + return false; + } + return $rest->response; + } + + + /** + * Get object information + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param boolean $returnInfo Return response information + * @return mixed | false + */ + public static function getObjectInfo($bucket, $uri, $returnInfo = true) { + $rest = new S3Request('HEAD', $bucket, $uri); + $rest = $rest->getResponse(); + if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404)) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false; + } + + + /** + * Copy an object + * + * @param string $bucket Source bucket name + * @param string $uri Source object URI + * @param string $bucket Destination bucket name + * @param string $uri Destination object URI + * @param constant $acl ACL constant + * @param array $metaHeaders Optional array of x-amz-meta-* headers + * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.) + * @return mixed | false + */ + public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array()) { + $rest = new S3Request('PUT', $bucket, $uri); + $rest->setHeader('Content-Length', 0); + foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v); + foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v); + $rest->setAmzHeader('x-amz-acl', $acl); + $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, $srcUri)); + if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0) + $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE'); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return isset($rest->body->LastModified, $rest->body->ETag) ? array( + 'time' => strtotime((string)$rest->body->LastModified), + 'hash' => substr((string)$rest->body->ETag, 1, -1) + ) : false; + } + + + /** + * Set logging for a bucket + * + * @param string $bucket Bucket name + * @param string $targetBucket Target bucket (where logs are stored) + * @param string $targetPrefix Log prefix (e,g; domain.com-) + * @return boolean + */ + public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null) { + // The S3 log delivery group has to be added to the target bucket's ACP + if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false) { + // Only add permissions to the target bucket when they do not exist + $aclWriteSet = false; + $aclReadSet = false; + foreach ($acp['acl'] as $acl) + if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery') { + if ($acl['permission'] == 'WRITE') $aclWriteSet = true; + elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true; + } + if (!$aclWriteSet) $acp['acl'][] = array( + 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE' + ); + if (!$aclReadSet) $acp['acl'][] = array( + 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP' + ); + if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp); + } + + $dom = new DOMDocument; + $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus'); + $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/'); + if ($targetBucket !== null) { + if ($targetPrefix == null) $targetPrefix = $bucket . '-'; + $loggingEnabled = $dom->createElement('LoggingEnabled'); + $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket)); + $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix)); + // TODO: Add TargetGrants? + $bucketLoggingStatus->appendChild($loggingEnabled); + } + $dom->appendChild($bucketLoggingStatus); + + $rest = new S3Request('PUT', $bucket, ''); + $rest->setParameter('logging', null); + $rest->data = $dom->saveXML(); + $rest->size = strlen($rest->data); + $rest->setHeader('Content-Type', 'application/xml'); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::setBucketLogging({$bucket}, {$uri}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Get logging status for a bucket + * + * This will return false if logging is not enabled. + * Note: To enable logging, you also need to grant write access to the log group + * + * @param string $bucket Bucket name + * @return array | false + */ + public static function getBucketLogging($bucket) { + $rest = new S3Request('GET', $bucket, ''); + $rest->setParameter('logging', null); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::getBucketLogging({$bucket}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + if (!isset($rest->body->LoggingEnabled)) return false; // No logging + return array( + 'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket, + 'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix, + ); + } + + + /** + * Disable bucket logging + * + * @param string $bucket Bucket name + * @return boolean + */ + public static function disableBucketLogging($bucket) { + return self::setBucketLogging($bucket, null); + } + + + /** + * Get a bucket's location + * + * @param string $bucket Bucket name + * @return string | false + */ + public static function getBucketLocation($bucket) { + $rest = new S3Request('GET', $bucket, ''); + $rest->setParameter('location', null); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::getBucketLocation({$bucket}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US'; + } + + + /** + * Set object or bucket Access Control Policy + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy) + * @return boolean + */ + public static function setAccessControlPolicy($bucket, $uri = '', $acp = array()) { + $dom = new DOMDocument; + $dom->formatOutput = true; + $accessControlPolicy = $dom->createElement('AccessControlPolicy'); + $accessControlList = $dom->createElement('AccessControlList'); + + // It seems the owner has to be passed along too + $owner = $dom->createElement('Owner'); + $owner->appendChild($dom->createElement('ID', $acp['owner']['id'])); + $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name'])); + $accessControlPolicy->appendChild($owner); + + foreach ($acp['acl'] as $g) { + $grant = $dom->createElement('Grant'); + $grantee = $dom->createElement('Grantee'); + $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + if (isset($g['id'])) { // CanonicalUser (DisplayName is omitted) + $grantee->setAttribute('xsi:type', 'CanonicalUser'); + $grantee->appendChild($dom->createElement('ID', $g['id'])); + } elseif (isset($g['email'])) { // AmazonCustomerByEmail + $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail'); + $grantee->appendChild($dom->createElement('EmailAddress', $g['email'])); + } elseif ($g['type'] == 'Group') { // Group + $grantee->setAttribute('xsi:type', 'Group'); + $grantee->appendChild($dom->createElement('URI', $g['uri'])); + } + $grant->appendChild($grantee); + $grant->appendChild($dom->createElement('Permission', $g['permission'])); + $accessControlList->appendChild($grant); + } + + $accessControlPolicy->appendChild($accessControlList); + $dom->appendChild($accessControlPolicy); + + $rest = new S3Request('PUT', $bucket, $uri); + $rest->setParameter('acl', null); + $rest->data = $dom->saveXML(); + $rest->size = strlen($rest->data); + $rest->setHeader('Content-Type', 'application/xml'); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Get object or bucket Access Control Policy + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @return mixed | false + */ + public static function getAccessControlPolicy($bucket, $uri = '') { + $rest = new S3Request('GET', $bucket, $uri); + $rest->setParameter('acl', null); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + + $acp = array(); + if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName)) { + $acp['owner'] = array( + 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName + ); + } + if (isset($rest->body->AccessControlList)) { + $acp['acl'] = array(); + foreach ($rest->body->AccessControlList->Grant as $grant) { + foreach ($grant->Grantee as $grantee) { + if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser + $acp['acl'][] = array( + 'type' => 'CanonicalUser', + 'id' => (string)$grantee->ID, + 'name' => (string)$grantee->DisplayName, + 'permission' => (string)$grant->Permission + ); + elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail + $acp['acl'][] = array( + 'type' => 'AmazonCustomerByEmail', + 'email' => (string)$grantee->EmailAddress, + 'permission' => (string)$grant->Permission + ); + elseif (isset($grantee->URI)) // Group + $acp['acl'][] = array( + 'type' => 'Group', + 'uri' => (string)$grantee->URI, + 'permission' => (string)$grant->Permission + ); + else continue; + } + } + } + return $acp; + } + + + /** + * Delete an object + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @return boolean + */ + public static function deleteObject($bucket, $uri) { + $rest = new S3Request('DELETE', $bucket, $uri); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 204) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::deleteObject(): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Get a query string authenticated URL + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param integer $lifetime Lifetime in seconds + * @param boolean $hostBucket Use the bucket name as the hostname + * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification) + * @return string + */ + public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false) { + $expires = time() + $lifetime; + $uri = str_replace('%2F', '/', rawurlencode($uri)); // URI should be encoded (thanks Sean O'Dea) + return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s', + $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires, + urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}"))); + } + + /** + * Get upload POST parameters for form uploads + * + * @param string $bucket Bucket name + * @param string $uriPrefix Object URI prefix + * @param constant $acl ACL constant + * @param integer $lifetime Lifetime in seconds + * @param integer $maxFileSize Maximum filesize in bytes (default 5MB) + * @param string $successRedirect Redirect URL or 200 / 201 status code + * @param array $amzHeaders Array of x-amz-meta-* headers + * @param array $headers Array of request headers or content type as a string + * @param boolean $flashVars Includes additional "Filename" variable posted by Flash + * @return object + */ + public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600, $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false) { + // Create policy object + $policy = new stdClass; + $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime)); + $policy->conditions = array(); + $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj); + $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj); + + $obj = new stdClass; // 200 for non-redirect uploads + if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201))) + $obj->success_action_status = (string)$successRedirect; + else // URL + $obj->success_action_redirect = $successRedirect; + array_push($policy->conditions, $obj); + + array_push($policy->conditions, array('starts-with', '$key', $uriPrefix)); + if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', '')); + foreach (array_keys($headers) as $headerKey) + array_push($policy->conditions, array('starts-with', '$'.$headerKey, '')); + foreach ($amzHeaders as $headerKey => $headerVal) { + $obj = new stdClass; $obj->{$headerKey} = (string)$headerVal; array_push($policy->conditions, $obj); + } + array_push($policy->conditions, array('content-length-range', 0, $maxFileSize)); + $policy = base64_encode(str_replace('\/', '/', json_encode($policy))); + + // Create parameters + $params = new stdClass; + $params->AWSAccessKeyId = self::$__accessKey; + $params->key = $uriPrefix.'${filename}'; + $params->acl = $acl; + $params->policy = $policy; unset($policy); + $params->signature = self::__getHash($params->policy); + if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201))) + $params->success_action_status = (string)$successRedirect; + else + $params->success_action_redirect = $successRedirect; + foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal; + foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal; + return $params; + } + + /** + * Create a CloudFront distribution + * + * @param string $bucket Bucket name + * @param boolean $enabled Enabled (true/false) + * @param array $cnames Array containing CNAME aliases + * @param string $comment Use the bucket name as the hostname + * @return array | false + */ + public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = '') { + self::$useSSL = true; // CloudFront requires SSL + $rest = new S3Request('POST', '', '2008-06-30/distribution', 'cloudfront.amazonaws.com'); + $rest->data = self::__getCloudFrontDistributionConfigXML($bucket.'.s3.amazonaws.com', $enabled, $comment, (string)microtime(true), $cnames); + $rest->size = strlen($rest->data); + $rest->setHeader('Content-Type', 'application/xml'); + $rest = self::__getCloudFrontResponse($rest); + + if ($rest->error === false && $rest->code !== 201) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", '$comment'): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } elseif ($rest->body instanceof SimpleXMLElement) + return self::__parseCloudFrontDistributionConfig($rest->body); + return false; + } + + + /** + * Get CloudFront distribution info + * + * @param string $distributionId Distribution ID from listDistributions() + * @return array | false + */ + public static function getDistribution($distributionId) { + self::$useSSL = true; // CloudFront requires SSL + $rest = new S3Request('GET', '', '2008-06-30/distribution/'.$distributionId, 'cloudfront.amazonaws.com'); + $rest = self::__getCloudFrontResponse($rest); + + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::getDistribution($distributionId): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } elseif ($rest->body instanceof SimpleXMLElement) { + $dist = self::__parseCloudFrontDistributionConfig($rest->body); + $dist['hash'] = $rest->headers['hash']; + return $dist; + } + return false; + } + + + /** + * Update a CloudFront distribution + * + * @param array $dist Distribution array info identical to output of getDistribution() + * @return array | false + */ + public static function updateDistribution($dist) { + self::$useSSL = true; // CloudFront requires SSL + $rest = new S3Request('PUT', '', '2008-06-30/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com'); + $rest->data = self::__getCloudFrontDistributionConfigXML($dist['origin'], $dist['enabled'], $dist['comment'], $dist['callerReference'], $dist['cnames']); + $rest->size = strlen($rest->data); + $rest->setHeader('If-Match', $dist['hash']); + $rest = self::__getCloudFrontResponse($rest); + + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::updateDistribution({$dist['id']}, ".(int)$enabled.", '$comment'): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } else { + $dist = self::__parseCloudFrontDistributionConfig($rest->body); + $dist['hash'] = $rest->headers['hash']; + return $dist; + } + return false; + } + + + /** + * Delete a CloudFront distribution + * + * @param array $dist Distribution array info identical to output of getDistribution() + * @return boolean + */ + public static function deleteDistribution($dist) { + self::$useSSL = true; // CloudFront requires SSL + $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com'); + $rest->setHeader('If-Match', $dist['hash']); + $rest = self::__getCloudFrontResponse($rest); + + if ($rest->error === false && $rest->code !== 204) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Get a list of CloudFront distributions + * + * @return array + */ + public static function listDistributions() { + self::$useSSL = true; // CloudFront requires SSL + $rest = new S3Request('GET', '', '2008-06-30/distribution', 'cloudfront.amazonaws.com'); + $rest = self::__getCloudFrontResponse($rest); + + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::listDistributions(): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary)) { + $list = array(); + if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated)) { + //$info['marker'] = (string)$rest->body->Marker; + //$info['maxItems'] = (int)$rest->body->MaxItems; + //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false; + } + foreach ($rest->body->DistributionSummary as $summary) { + $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary); + } + return $list; + } + return array(); + } + + + /** + * Get a DistributionConfig DOMDocument + * + * @internal Used to create XML in createDistribution() and updateDistribution() + * @param string $bucket Origin bucket + * @param boolean $enabled Enabled (true/false) + * @param string $comment Comment to append + * @param string $callerReference Caller reference + * @param array $cnames Array of CNAME aliases + * @return string + */ + private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array()) { + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + $distributionConfig = $dom->createElement('DistributionConfig'); + $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2008-06-30/'); + $distributionConfig->appendChild($dom->createElement('Origin', $bucket)); + $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference)); + foreach ($cnames as $cname) + $distributionConfig->appendChild($dom->createElement('CNAME', $cname)); + if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment)); + $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false')); + $dom->appendChild($distributionConfig); + return $dom->saveXML(); + } + + + /** + * Parse a CloudFront distribution config + * + * @internal Used to parse the CloudFront DistributionConfig node to an array + * @param object &$node DOMNode + * @return array + */ + private static function __parseCloudFrontDistributionConfig(&$node) { + $dist = array(); + if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName)) { + $dist['id'] = (string)$node->Id; + $dist['status'] = (string)$node->Status; + $dist['time'] = strtotime((string)$node->LastModifiedTime); + $dist['domain'] = (string)$node->DomainName; + } + if (isset($node->CallerReference)) + $dist['callerReference'] = (string)$node->CallerReference; + if (isset($node->Comment)) + $dist['comment'] = (string)$node->Comment; + if (isset($node->Enabled, $node->Origin)) { + $dist['origin'] = (string)$node->Origin; + $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false; + } elseif (isset($node->DistributionConfig)) { + $dist = array_merge($dist, self::__parseCloudFrontDistributionConfig($node->DistributionConfig)); + } + if (isset($node->CNAME)) { + $dist['cnames'] = array(); + foreach ($node->CNAME as $cname) $dist['cnames'][(string)$cname] = (string)$cname; + } + return $dist; + } + + + /** + * Grab CloudFront response + * + * @internal Used to parse the CloudFront S3Request::getResponse() output + * @param object &$rest S3Request instance + * @return object + */ + private static function __getCloudFrontResponse(&$rest) { + $rest->getResponse(); + if ($rest->response->error === false && isset($rest->response->body) && + is_string($rest->response->body) && substr($rest->response->body, 0, 5) == 'response->body = simplexml_load_string($rest->response->body); + // Grab CloudFront errors + if (isset($rest->response->body->Error, $rest->response->body->Error->Code, + $rest->response->body->Error->Message)) { + $rest->response->error = array( + 'code' => (string)$rest->response->body->Error->Code, + 'message' => (string)$rest->response->body->Error->Message + ); + unset($rest->response->body); + } + } + return $rest->response; + } + + + /** + * Get MIME type for file + * + * @internal Used to get mime types + * @param string &$file File path + * @return string + */ + public static function __getMimeType(&$file) { + $type = false; + // Fileinfo documentation says fileinfo_open() will use the + // MAGIC env var for the magic file + if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) && + ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false) { + if (($type = finfo_file($finfo, $file)) !== false) { + // Remove the charset and grab the last content-type + $type = explode(' ', str_replace('; charset=', ';charset=', $type)); + $type = array_pop($type); + $type = explode(';', $type); + $type = trim(array_shift($type)); + } + finfo_close($finfo); + + // If anyone is still using mime_content_type() + } elseif (function_exists('mime_content_type')) + $type = trim(mime_content_type($file)); + + if ($type !== false && strlen($type) > 0) return $type; + + // Otherwise do it the old fashioned way + static $exts = array( + 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png', + 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon', + 'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf', + 'zip' => 'application/zip', 'gz' => 'application/x-gzip', + 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', 'txt' => 'text/plain', + 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html', + 'css' => 'text/css', 'js' => 'text/javascript', + 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml', + 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav', + 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg', + 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php' + ); + $ext = strtolower(pathInfo($file, PATHINFO_EXTENSION)); + return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream'; + } + + + /** + * Generate the auth string: "AWS AccessKey:Signature" + * + * @internal Used by S3Request::getResponse() + * @param string $string String to sign + * @return string + */ + public static function __getSignature($string) { + return 'AWS '.self::$__accessKey.':'.self::__getHash($string); + } + + + /** + * Creates a HMAC-SHA1 hash + * + * This uses the hash extension if loaded + * + * @internal Used by __getSignature() + * @param string $string String to sign + * @return string + */ + private static function __getHash($string) { + return base64_encode(extension_loaded('hash') ? + hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1( + (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) . + pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^ + (str_repeat(chr(0x36), 64))) . $string))))); + } + +} + +final class S3Request { + private $verb, $bucket, $uri, $resource = '', $parameters = array(), + $amzHeaders = array(), $headers = array( + 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => '' + ); + public $fp = false, $size = 0, $data = false, $response; + + + /** + * Constructor + * + * @param string $verb Verb + * @param string $bucket Bucket name + * @param string $uri Object URI + * @return mixed + */ + function __construct($verb, $bucket = '', $uri = '', $defaultHost = 's3.amazonaws.com') { + $this->verb = $verb; + $this->bucket = strtolower($bucket); + $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/'; + + if ($this->bucket !== '') { + $this->headers['Host'] = $this->bucket.'.'.$defaultHost; + $this->resource = '/'.$this->bucket.$this->uri; + } else { + $this->headers['Host'] = $defaultHost; + //$this->resource = strlen($this->uri) > 1 ? '/'.$this->bucket.$this->uri : $this->uri; + $this->resource = $this->uri; + } + $this->headers['Date'] = gmdate('D, d M Y H:i:s T'); + + $this->response = new STDClass; + $this->response->error = false; + } + + + /** + * Set request parameter + * + * @param string $key Key + * @param string $value Value + * @return void + */ + public function setParameter($key, $value) { + $this->parameters[$key] = $value; + } + + + /** + * Set request header + * + * @param string $key Key + * @param string $value Value + * @return void + */ + public function setHeader($key, $value) { + $this->headers[$key] = $value; + } + + + /** + * Set x-amz-meta-* header + * + * @param string $key Key + * @param string $value Value + * @return void + */ + public function setAmzHeader($key, $value) { + $this->amzHeaders[$key] = $value; + } + + + /** + * Get the S3 response + * + * @return object | false + */ + public function getResponse() { + $query = ''; + if (sizeof($this->parameters) > 0) { + $query = substr($this->uri, -1) !== '?' ? '?' : '&'; + foreach ($this->parameters as $var => $value) + if ($value == null || $value == '') $query .= $var.'&'; + // Parameters should be encoded (thanks Sean O'Dea) + else $query .= $var.'='.rawurlencode($value).'&'; + $query = substr($query, 0, -1); + $this->uri .= $query; + + if (array_key_exists('acl', $this->parameters) || + array_key_exists('location', $this->parameters) || + array_key_exists('torrent', $this->parameters) || + array_key_exists('logging', $this->parameters)) + $this->resource .= $query; + } + $url = ((S3::$useSSL && extension_loaded('openssl')) ? + 'https://':'http://').$this->headers['Host'].$this->uri; + //var_dump($this->bucket, $this->uri, $this->resource, $url); + + // Basic setup + $curl = curl_init(); + curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php'); + + if (S3::$useSSL) { + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1); + } + + curl_setopt($curl, CURLOPT_URL, $url); + + // Headers + $headers = array(); $amz = array(); + foreach ($this->amzHeaders as $header => $value) + if (strlen($value) > 0) $headers[] = $header.': '.$value; + foreach ($this->headers as $header => $value) + if (strlen($value) > 0) $headers[] = $header.': '.$value; + + // Collect AMZ headers for signature + foreach ($this->amzHeaders as $header => $value) + if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value; + + // AMZ headers must be sorted + if (sizeof($amz) > 0) { + sort($amz); + $amz = "\n".implode("\n", $amz); + } else $amz = ''; + + // Authorization string (CloudFront stringToSign should only contain a date) + $headers[] = 'Authorization: ' . S3::__getSignature( + $this->headers['Host'] == 'cloudfront.amazonaws.com' ? $this->headers['Date'] : + $this->verb."\n".$this->headers['Content-MD5']."\n". + $this->headers['Content-Type']."\n".$this->headers['Date'].$amz."\n".$this->resource + ); + + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLOPT_HEADER, false); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, false); + curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback')); + curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback')); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + + // Request types + switch ($this->verb) { + case 'GET': break; + case 'PUT': case 'POST': // POST only used for CloudFront + if ($this->fp !== false) { + curl_setopt($curl, CURLOPT_PUT, true); + curl_setopt($curl, CURLOPT_INFILE, $this->fp); + if ($this->size >= 0) + curl_setopt($curl, CURLOPT_INFILESIZE, $this->size); + } elseif ($this->data !== false) { + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb); + curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data); + if ($this->size >= 0) + curl_setopt($curl, CURLOPT_BUFFERSIZE, $this->size); + } else + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb); + break; + case 'HEAD': + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD'); + curl_setopt($curl, CURLOPT_NOBODY, true); + break; + case 'DELETE': + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE'); + break; + default: break; + } + + // Execute, grab errors + if (curl_exec($curl)) + $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + else + $this->response->error = array( + 'code' => curl_errno($curl), + 'message' => curl_error($curl), + 'resource' => $this->resource + ); + + @curl_close($curl); + + // Parse body into XML + if ($this->response->error === false && isset($this->response->headers['type']) && + $this->response->headers['type'] == 'application/xml' && isset($this->response->body)) { + $this->response->body = simplexml_load_string($this->response->body); + + // Grab S3 errors + if (!in_array($this->response->code, array(200, 204)) && + isset($this->response->body->Code, $this->response->body->Message)) { + $this->response->error = array( + 'code' => (string)$this->response->body->Code, + 'message' => (string)$this->response->body->Message + ); + if (isset($this->response->body->Resource)) + $this->response->error['resource'] = (string)$this->response->body->Resource; + unset($this->response->body); + } + } + + // Clean up file resources + if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp); + + return $this->response; + } + + + /** + * CURL write callback + * + * @param resource &$curl CURL resource + * @param string &$data Data + * @return integer + */ + private function __responseWriteCallback(&$curl, &$data) { + if ($this->response->code == 200 && $this->fp !== false) + return fwrite($this->fp, $data); + else + $this->response->body .= $data; + return strlen($data); + } + + + /** + * CURL header callback + * + * @param resource &$curl CURL resource + * @param string &$data Data + * @return integer + */ + private function __responseHeaderCallback(&$curl, &$data) { + if (($strlen = strlen($data)) <= 2) return $strlen; + if (substr($data, 0, 4) == 'HTTP') + $this->response->code = (int)substr($data, 9, 3); + else { + list($header, $value) = explode(': ', trim($data), 2); + if ($header == 'Last-Modified') + $this->response->headers['time'] = strtotime($value); + elseif ($header == 'Content-Length') + $this->response->headers['size'] = (int)$value; + elseif ($header == 'Content-Type') + $this->response->headers['type'] = $value; + elseif ($header == 'ETag') + $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value; + elseif (preg_match('/^x-amz-meta-.*$/', $header)) + $this->response->headers[$header] = is_numeric($value) ? (int)$value : $value; + } + return $strlen; + } + +} diff --git a/plugins/video_s3/includes/amazon_s3.inc b/plugins/video_s3/includes/amazon_s3.inc new file mode 100644 index 0000000..d4a7ccc --- /dev/null +++ b/plugins/video_s3/includes/amazon_s3.inc @@ -0,0 +1,169 @@ +access_key = variable_get('amazon_s3_access_key', ''); + $this->secret_key = variable_get('amazon_s3_secret_access_key', ''); + $this->ssl = variable_get('amazon_s3_ssl', FALSE); + $this->limit = variable_get('amazon_s3_limit', 5); + $this->bucket = variable_get('amazon_s3_bucket', ''); + } + + public function connect($access_key = '', $secret_key = '', $ssl = FALSE) { + $access_key = $access_key ? $access_key : $this->access_key; + $secret_key = $secret_key ? $secret_key : $this->secret_key; + $ssl = $ssl ? $ssl : $this->ssl; + // Make our connection to Amazon. + $this->s3 = new S3($access_key, $secret_key, $ssl); + } + + /* + * Verifies the existence of a file id, returns the row or false if none found. + */ + + public function verify($fid) { + $sql = db_query("SELECT * FROM {video_s3} WHERE fid=%d", $fid); + $row = db_fetch_object($sql); + return $row; + } + + /* + * Gets a video object from the database. + */ + + public function get($fid) { + $sql = db_query("SELECT * FROM {video_s3} WHERE fid=%d AND status=%d", $fid, VIDEO_S3_COMPLETE); + $row = db_fetch_object($sql); + return $row; + } + + /* + * Inserts file object into the database. + */ + + public function insert($fid) { + db_query("INSERT INTO {video_s3} (fid, status) VALUES (%d, %d)", $fid, VIDEO_S3_PENDING); + } + + /* + * Updates the database after a successful transfer to amazon. + */ + + public function update($video) { + $result = db_query("UPDATE {video_s3} SET bucket='%s', filename='%s', filepath='%s', filemime='%s', filesize='%s', status=%d, completed=%d WHERE vid=%d", + $video->bucket, $video->filename, $video->filepath, $video->filemime, $video->filesize, VIDEO_S3_COMPLETE, time(), $video->vid); + return $result; + } + + public function working($vid) { + db_query("UPDATE {video_s3} SET status=%d WHERE vid=%d", VIDEO_S3_ACTIVE, $vid); + } + + public function failed($vid) { + db_query("UPDATE {video_s3} SET status=%d WHERE vid=%d", VIDEO_S3_FAILED, $vid); + } + + public function delete($fid) { + // Lets get our file no matter the status and delete it. + if ($video = $this->verify($fid)) { + if ($video->bucket) { + // It has been pushed to amazon so lets remove it. + $this->s3->deleteObject($video->bucket, $video->filename); + } + // Lets delete our record from the database. + db_query("DELETE FROM {video_s3} WHERE vid=%d", $video->vid); + } + } + + /* + * Selects the pending queue to be transfered to amazon. + */ + + public function queue() { + $video = false; + $sql = db_query("SELECT vid, fid FROM {video_s3} WHERE status=%d LIMIT %d", VIDEO_S3_PENDING, $this->limit); + while ($row = db_fetch_object($sql)) { + $video = false; + // We need to check if this file id exists in our transcoding table. + $sql_video = db_query("SELECT * FROM {video_files} WHERE fid=%d", $row->fid); + if ($sql_video_row = db_fetch_object($sql_video)) { + // This is a transcoded file, lets verify it has been transcoded and if so lets push it to amazon. + module_load_include('inc', 'video', '/includes/conversion'); + if ($sql_video_row->status == VIDEO_RENDERING_COMPLETE) { + $video = $sql_video_row; + } + } else { + // This is a regular video file, lets get our file object from the files table and push it to amazon. + $sql_files = db_query("SELECT * FROM {files} WHERE fid=%d", $row->fid); + if ($sql_files_row = db_fetch_object($sql_files)) { + $video = $sql_files_row; + } + } + // If we have a video lets go ahead and send it. + if ($video) { + // Update our status to working. + $this->working($row->vid); + $filepath = $video->filepath; + // get the folder path as file object + $filename = $video->filepath; + // use the file object as file name + $video->filename = $filename; + $perm = (variable_get('amazon_s3_private', FALSE) == FALSE) ? S3::ACL_PUBLIC_READ : S3::ACL_PRIVATE; + + if ($this->s3->putObjectFile($filepath, $this->bucket, $filename, $perm)) { + // Update our table. + $video->bucket = $this->bucket; + $video->vid = $row->vid; + $prefix = $this->ssl ? 'https://' : 'http://'; + $video->filepath = $prefix . $video->bucket . '.s3.amazonaws.com/' . $filename; + if ($this->update($video)) { + watchdog('amazon_s3', t('Successfully uploaded our file: !file into the bucket %bucket on the Amazon S3 server.', array('!file' => $filepath, '%bucket' => $this->bucket)), array(), WATCHDOG_INFO); + } + } else { + watchdog('amazon_s3', 'Failed to upload our file to the amazon s3 server.', array(), WATCHDOG_ERROR); + $this->failed($row->vid); + } + } else { + watchdog('amazon_s3', 'We did not find the file id: ' . $row->fid . ' or it is still queued for ffmpeg processing.', array(), WATCHDOG_DEBUG); + } + } + } + + public function get_object_info($object) { + return $this->s3->getObjectInfo($this->bucket, $object); + } + + public function get_authenticated_url($object) { + $lifetime = variable_get('amazon_s3_lifetime', '1800'); + return $this->s3->getAuthenticatedURL($this->bucket, $object, $lifetime); + } + + public function get_object($object, $saveTo = false) { + return $this->s3->getObject($this->bucket, $object, $saveTo); + } + +} \ No newline at end of file diff --git a/plugins/video_s3/video_s3.info b/plugins/video_s3/video_s3.info new file mode 100644 index 0000000..57898fc --- /dev/null +++ b/plugins/video_s3/video_s3.info @@ -0,0 +1,8 @@ +;$Id$ + +name = Amazon S3 on Video +description = Leverages the Video module and Amazon Simple Storage Service (Amazon S3) to serve and store your video's saving bandwidth. +package = "Video" +dependencies[] = video +core = 6.x +version = 6.x-4.x-dev \ No newline at end of file diff --git a/plugins/video_s3/video_s3.install b/plugins/video_s3/video_s3.install new file mode 100644 index 0000000..5803a8a --- /dev/null +++ b/plugins/video_s3/video_s3.install @@ -0,0 +1,107 @@ + t('Store video s3 cdn'), + 'fields' => array( + 'vid' => array( + 'description' => t('Auto Increment id'), + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'fid' => array( + 'description' => t('Original file id'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'nid' => array( + 'description' => t('Node id'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'bucket' => array( + 'type' => 'varchar', + 'length' => '255', + 'default' => '', + 'description' => t('The bucket the video is stored in.'), + ), + 'filename' => array( + 'type' => 'varchar', + 'length' => '255', + 'default' => '', + 'description' => t('The filename of the video.'), + ), + 'filepath' => array( + 'type' => 'varchar', + 'length' => '255', + 'default' => '', + 'description' => t('The filepath of the video.'), + ), + 'filemime' => array( + 'type' => 'varchar', + 'length' => '255', + 'default' => '', + 'description' => t('The filemime of the video.'), + ), + 'filesize' => array( + 'description' => t('Filesize of the video.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'status' => array( + 'description' => t('Status of the cdn transfer'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'completed' => array( + 'description' => t('Time of successful completion to amazon.'), + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'status' => array('status'), + 'file' => array('fid'), + ), + 'primary key' => array('vid'), + ); + return $schema; +} + +/** + * Implementation of hook_install(). + */ +function video_s3_install() { + drupal_install_schema('video_s3'); +} + +/** + * Implementation of hook_uninstall(). + */ +function video_s3_uninstall() { + drupal_uninstall_schema('video_s3'); + // Delete our variables. + variable_del('amazon_s3'); + variable_del('amazon_s3_ssl'); + variable_del('amazon_s3_access_key'); + variable_del('amazon_s3_secret_access_key'); + variable_del('amazon_s3_bucket'); +} diff --git a/plugins/video_s3/video_s3.module b/plugins/video_s3/video_s3.module new file mode 100644 index 0000000..274da26 --- /dev/null +++ b/plugins/video_s3/video_s3.module @@ -0,0 +1,168 @@ + 'Delete Bucket', + 'page callback' => 'video_s3_bucket_delete', + 'page arguments' => array(5), + 'access arguments' => array('administer amazon s3'), + 'file' => 'video_s3.admin.inc', + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/* + * Implementation of hook_cron(). + */ + +function video_s3_cron() { + $filesystem = variable_get('vid_filesystem', 'drupal'); + if ($filesystem == 'video_s3') { + module_load_include('inc', 'video_s3', '/includes/amazon_s3'); + $s3 = new video_amazon_s3; + $s3->connect(); + // Lets run our queue. + $s3->queue(); + } +} + +/** + * Implementation of hook_video_delete. + * we can use hook_file_delete() + */ +function video_s3_video_delete($file) { + module_load_include('inc', 'video_s3', '/includes/amazon_s3'); + $s3 = new video_amazon_s3; + $s3->connect(); + // Lets run our queue. + $s3->delete($file->fid); +} + +/* + * Implementation of hook_video_update. + * Submit hanlder to update our s3 table to include the node id. + */ + +function video_s3_video_update($form, &$form_state) { + //lets update our video rending table to include the node id created + if (isset($form_state['nid']) && isset($form_state['values']['video_id']) && is_array($form_state['values']['video_id'])) { + foreach ($form_state['values']['video_id'] as $fid) { + //lets update our table to include the nid + db_query("UPDATE {video_s3} SET nid=%d WHERE fid=%d", $form_state['nid'], $fid); + } + } +} + +/** + * Implementing hook_video_insert + * @param $element + * @param $form_state + */ +function video_s3_video_insert(&$element, &$form_state) { + $file = $element['#value']; + //we need to check if this fid has already been added to the database AND that there is in fact a fid + if (is_array($file) && isset($file['fid']) && !empty($file['fid'])) { + module_load_include('inc', 'video_s3', '/includes/amazon_s3'); + $s3 = new video_amazon_s3; + $s3->connect(); + // Lets verify that we haven't added this video already. Multiple validation fails will cause this to be ran more than once + if (!$video = $s3->verify($file['fid'])) { + // Video has not been added to the queue yet so lets add it. + $s3->insert($file['fid']); + drupal_set_message(t('Video submission queued for transfer to your Amazon S3 server. Will be there shortly.')); + } + } +} + +/** + * Implementing hook_video_load + * @param $element + * @param $form_state + */ +function video_s3_video_load(&$video) { + module_load_include('inc', 'video_s3', '/includes/amazon_s3'); + $s3 = new video_amazon_s3; + if ($amazon = $s3->get($video->fid)) { + // Fix our filepath + $video->filepath = $amazon->filepath; +// $video->url = $amazon->filepath; + if (variable_get('amazon_s3_private', FALSE)) + $video->files->{$video->player}->url = video_s3_get_authenticated_url($amazon->filename); + else + $video->files->{$video->player}->url = $amazon->filepath; + + $video->extension = pathinfo($amazon->filepath, PATHINFO_EXTENSION); + } +} + +function video_s3_get_object_info($object) { + module_load_include('inc', 'video_s3', '/includes/amazon_s3'); + $s3 = new video_amazon_s3; + $s3->connect(); + return $s3->get_object_info($object); +} + +function video_s3_get_authenticated_url($object) { + module_load_include('inc', 'video_s3', '/includes/amazon_s3'); + $s3 = new video_amazon_s3; + $s3->connect(); + return $s3->get_authenticated_url($object); +} + +function video_s3_get_object($object, $save_to = false) { + module_load_include('inc', 'video_s3', '/includes/amazon_s3'); + $s3 = new video_amazon_s3; + $s3->connect(); + return $s3->get_object($object, $save_to); +} + +/* + * Deletes a bucket from your Amazon S3 server. + */ + +function video_s3_bucket_delete($bucket) { + module_load_include('inc', 'video_s3', '/includes/amazon_s3'); + $s3 = new video_amazon_s3; + $s3->connect(); + $buckets = $s3->s3->listBuckets(); + if (is_array($buckets) && in_array($bucket, $buckets)) { + if ($s3->s3->deleteBucket($bucket)) { + drupal_set_message(t('Successfully deleted the bucket %bucket', array('%bucket' => $bucket))); + } else { + drupal_set_message(t('Could not delete the bucket %bucket', array('%bucket' => $bucket)), 'error'); + } + } else { + drupal_set_message(t('The bucket %bucket does not exist for deletion.', array('%bucket' => $bucket)), 'error'); + } + drupal_goto('admin/settings/video/amazon_s3'); +} \ No newline at end of file diff --git a/plugins/video_zencoder/includes/LICENSE b/plugins/video_zencoder/includes/LICENSE new file mode 100644 index 0000000..d1f0a51 --- /dev/null +++ b/plugins/video_zencoder/includes/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2010 Zencoder + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/video_zencoder/includes/README.markdown b/plugins/video_zencoder/includes/README.markdown new file mode 100644 index 0000000..0be2256 --- /dev/null +++ b/plugins/video_zencoder/includes/README.markdown @@ -0,0 +1,199 @@ +Zencoder API PHP Library +========================== + +Author: [Steve Heffernan](http://www.steveheffernan.com) (steve (a) zencoder (.) com) +Company: [Zencoder - Online Video Encoder](http://zencoder.com) +Version: 1.1 +Date: 2010-06-04 +Repository: + +For more details on the Zencoder API requirements visit + + + +ENCODING JOB +------------ +The ZencoderJob object creates an encoding job using [cURL](http://zencoder.com/docs/glossary/curl/) +to send [JSON](http://zencoder.com/docs/glossary/json/) formatted parameters to Zencoder's encoding API. + +### Step 1 +Visit the [API builder](https://app.zencoder.com/api_builder) in your account, +and execute a successful encoding job. + +### Step 2 +Copy the successful JSON string, starting with the first curly brace "{", +and pass it as the parameters for a new ZencoderJob object. Execute the script on your server to test that it works. + +#### Example +
+    created) {
+      // Success
+      echo "w00t! \n\n";
+      echo "Job ID: ".$encoding_job->id."\n";
+      echo "Output '".$encoding_job->outputs["web"]->label."' ID: ".$encoding_job->outputs["web"]->id."\n";
+      // Store Job/Output IDs to update their status when notified or to check their progress.
+    } else {
+      // Failed
+      echo "Fail :(\n\n";
+      echo "Errors:\n";
+      foreach($encoding_job->errors as $error) {
+        echo $error."\n";
+      }
+    }
+
+    echo "\nAll Job Attributes:\n";
+    var_dump($encoding_job);
+
+    ?>
+    
+ +### Step 3 +Modify the above script to meet your needs. +Your [API Request History](https://app.zencoder.com/api_requests) may come in handy. +You can revisit your [API builder](https://app.zencoder.com/api_builder) to add/update parameters of the JSON. + +You can translate the JSON string into nested associative arrays so that you can dynamically change attributes like "input". +The previous JSON example would become: + + $encoding_job = new ZencoderJob(array( + "api_key" => "93h630j1dsyshjef620qlkavnmzui3", + "input" => "s3://bucket-name/file-name.avi", + "outputs" => array( + array( + "label" => "web" + ) + ) + )); + + +GENERAL API REQUESTS +-------------------- +A general API request can be used for all API functionality including **Job Listing**, **Job Details**, **Account Creation**, **Account Details** (even Job Creation if desired). See the [API docs](http://zencoder.com/docs/api/) for all possible API requests. +The first argument is the **API URL**. +The second argument is your **API Key**. +The third argument is the **request parameters** if needed. It can either be a JSON string or an array of parameters. + + +#### Example Job List Request + + $request = new ZencoderRequest( + 'https://app.zencoder/api/jobs', + '93h630j1dsyshjef620qlkavnmzui3' + ); + + if ($request->successful) { + print_r($request->results); + } else { + foreach($request->errors as $error) { + echo $error."\n"; + } + } + +#### Example Account Creation Request + + $request = new ZencoderRequest( + 'https://app.zencoder/api/account', + false, // API key isn't needed for new account creation + array( + "terms_of_service" => "1", + "email" => "test@example.com", + "password" => "1234" + ) + ); + + if ($request->successful) { + print_r($request->results); + } else { + foreach($request->errors as $error) { + echo $error."\n"; + } + } + + +NOTIFICATION HANDLING +---------------------- +The ZencoderOutputNotification class is used to capture and parse JSON data sent from +Zencoder to your app when an output file has been completed. + + + +### Step 1 +Create a script to receive notifications, and upload it to a location on your server that is publicly accessible. + +#### Example + output->state == "finished") { + echo "w00t!\n"; + + // If you're encoding to multiple outputs and only care when all of the outputs are finished + // you can check if the entire job is finished. + if($notification->job->state == "finished") { + echo "Dubble w00t!"; + } + } elseif ($notification->output->state == "cancelled") { + echo "Cancelled!\n"; + } else { + echo "Fail!\n"; + echo $notification->output->error_message."\n"; + echo $notification->output->error_link; + } + + ?> + +### Step 2 +In the parameters for an encoding job, add the URL for your script to the notifications array of each output you want to be notified for. +Then submit the job to test if it works. + +**You can view the results at:** + + +#### Example + ... + "outputs" => array( + array( + "label" => "web", + "notifications" => array("http://example.com.com/encoding/notification.php") + ), + array( + "label" => "iPhone", + "notifications" => array("http://example.com.com/encoding/notification.php") + ) + ) + ... + + +### Step 3 +Modify the above script to meet your needs. +Your [notifications page](https://app.zencoder.com/notifications) will come in handy. + +VERSIONS +--------- + Version 1.1 - 2010-06-04 Added General API Requests + Version 1.0 - 2010-04-02 Jobs and Notifications. \ No newline at end of file diff --git a/plugins/video_zencoder/includes/Zencoder.php b/plugins/video_zencoder/includes/Zencoder.php new file mode 100644 index 0000000..02b847f --- /dev/null +++ b/plugins/video_zencoder/includes/Zencoder.php @@ -0,0 +1,240 @@ +encode($value); } + function json_decode($value) { return $GLOBALS['JSON_OBJECT']->decode($value); } +} + +class ZencoderJob { + + var $new_job_url = "https://app.zencoder.com/api/jobs"; //https://app.zencoder.com/api/jobs + //https://zencoder-staging.heroku.com/api/jobs + var $new_job_params = array(); + var $new_job_json; + var $created = false; + var $errors = array(); + + // Attributes + var $id; + var $outputs = array(); + + // Initialize + function ZencoderJob($params, $options = array()) { + + // Build using params if not sending request + if($options["build"]) { + $this->update_attributes($params); + return true; + } + + $this->new_job_params = $params; + $this->created = $this->create(); + } + + // Send Job Request to API + function create() { + // Send request + $request = new ZencoderRequest($this->new_job_url, false, $this->new_job_params); + + if($request->successful) { + $this->update_attributes($request->results); + return true; + } else { + $this->errors = array_merge($this->errors, $request->errors); + return false; + } + } + + // Add/Update attributes on the job object. + function update_attributes($attributes = array()) { + foreach($attributes as $attr_name => $attr_value) { + // Create output file objects + if($attr_name == "outputs" && is_array($attr_value)) { + $this->create_outputs($attr_value); + } elseif (!function_exists($this->$attr_name)) { + $this->$attr_name = $attr_value; + } + } + } + + // Create output file objects from returned parameters. + // Use the Label for the key if avaiable. + function create_outputs($outputs = array()) { + foreach($outputs as $output_attrs) { + if($output_attrs["label"]) { + $this->outputs[$output_attrs["label"]] = new ZencoderOutputFile($output_attrs); + } else { + $this->outputs[] = new ZencoderOutputFile($output_attrs); + } + } + } +} + +class ZencoderOutputFile { + + var $id; + var $label; + var $url; + var $state; + var $error_message; + var $error_link; + + function ZencoderOutputFile($attributes = array()) { + $this->update_attributes($attributes); + } + + // Add/Update attributes on the file object. + function update_attributes($attributes = array()) { + foreach($attributes as $attr_name => $attr_value) { + if(!function_exists($this->$attr_name)) { + $this->$attr_name = $attr_value; + } + } + } +} + +// General API request class +class ZencoderRequest { + + var $successful = false; + var $errors = array(); + var $raw_results; + var $results; + + function ZencoderRequest($url, $api_key = "", $params = "") { + + // Add api_key to url if supplied + if($api_key) { + $url .= "?api_key=".$api_key; + } + + // Get JSON + if(is_string($params)) { + $json = trim($params); + } else if(is_array($params)) { + $json = json_encode($params); + } else { + $json = false; + } + + // Create request + $request = new ZencoderCURL($url, $json); + + // Check for connection errors + if ($request->connected == false) { + $this->errors[] = $request->error; + return; + } + + $status_code = intval($request->status_code); + $this->raw_results = $request->results; + + // Parse returned JSON + $this->results = json_decode($this->raw_results, true); + + // Return based on HTTP status code + if($status_code >= 200 && $status_code <= 206) { + $this->successful = true; + } else { + // Get job request errors if any + if(is_array($this->results["errors"])) { + foreach($this->results["errors"] as $error) { + $this->errors[] = $error; + } + } else { + $this->errors[] = "Unknown Error\n\nHTTP Status Code: ".$request->status_code."\n"."Raw Results: \n".$request->raw_results; + } + } + } +} + +// ZencoderCURL +// The connection class to perform the actual request to the surver +// using cURL http://php.net/manual/en/book.curl.php +class ZencoderCURL { + + var $options = array( + CURLOPT_RETURNTRANSFER => 1, // Return content of the url + CURLOPT_HEADER => 0, // Don't return the header in result + CURLOPT_HTTPHEADER => array("Content-Type: application/json", "Accept: application/json"), + CURLOPT_CONNECTTIMEOUT => 0, // Time in seconds to timeout send request. 0 is no timeout. + CURLOPT_FOLLOWLOCATION => 1, // Follow redirects. + ); + + var $connected; + var $results; + var $status_code; + var $error; + + // Initialize + function ZencoderCURL($url, $json, $options = array()) { + + // Add library details to request + $this->options[CURLOPT_HTTPHEADER][] = "Zencoder-Library-Name: ".ZENCODER_LIBRARY_NAME; + $this->options[CURLOPT_HTTPHEADER][] = "Zencoder-Library-Version: ".ZENCODER_LIBRARY_VERSION; + + // If posting data + if($json) { + $this->options[CURLOPT_POST] = 1; + $this->options[CURLOPT_POSTFIELDS] = $json; + } + + // Add cURL options to defaults (can't use array_merge) + foreach($options as $option_key => $option) { + $this->options[$option_key] = $option; + } + + // Initialize session + $ch = curl_init($url); + + // Set transfer options + curl_setopt_array($ch, $this->options); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + + // Execute session and store returned results + $this->results = curl_exec($ch); + + // Store the HTTP status code given (201, 404, etc.) + $this->status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + // Check for cURL error + if (curl_errno($ch)) { + $this->error = 'cURL connection error ('.curl_errno($ch).'): '.htmlspecialchars(curl_error($ch)).' Search'; + $this->connected = false; + } else { + $this->connected = true; + } + + // Close session + curl_close($ch); + } +} + +// Capture incoming notifications from Zencoder to your app +class ZencoderOutputNotification { + + var $output; + var $job; + + function ZencoderOutputNotification($params) { + if($params["output"]) $this->output = new ZencoderOutputFile($params["output"]); + if($params["job"]) $this->job = new ZencoderJob($params["job"], array("build" => true)); + } + + function catch_and_parse() { + $notificiation_data = json_decode(trim(file_get_contents('php://input')), true); + return new ZencoderOutputNotification($notificiation_data); + } +} diff --git a/plugins/video_zencoder/includes/lib/JSON.php b/plugins/video_zencoder/includes/lib/JSON.php new file mode 100644 index 0000000..229799b --- /dev/null +++ b/plugins/video_zencoder/includes/lib/JSON.php @@ -0,0 +1,806 @@ + + * @author Matt Knapp + * @author Brett Stimmerman + * @copyright 2005 Michal Migurski + * @version CVS: $Id$ + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * + * // create a new instance of Services_JSON + * $json = new Services_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * + */ +class Services_JSON +{ + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + */ + function Services_JSON($use = 0) + { + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + } + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + } + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $utf8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + } + + // treat it like a regular array + $elements = array_map(array($this, 'encode'), $var); + + foreach($elements as $element) { + if(Services_JSON::isError($element)) { + return $element; + } + } + + return '[' . join(',', $elements) . ']'; + + case 'object': + $vars = get_object_vars($var); + + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->encode($value); + + if(Services_JSON::isError($encoded_value)) { + return $encoded_value; + } + + return $this->encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + $m = array(); + + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + $parts = array(); + + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { + // found a quote, we're in a string, and it's not escaped + // we know that it's not escaped becase there is _not_ an + // odd number of backslashes at the end of the string so far + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } +} + +if (class_exists('PEAR_Error')) { + + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } + +} + +?> \ No newline at end of file diff --git a/plugins/video_zencoder/includes/zencoder.inc b/plugins/video_zencoder/includes/zencoder.inc new file mode 100644 index 0000000..3b9f305 --- /dev/null +++ b/plugins/video_zencoder/includes/zencoder.inc @@ -0,0 +1,254 @@ +access_key = variable_get('video_zencoder_api_key', ''); + $this->limit = variable_get('amazon_s3_limit', 5); + $this->bucket = variable_get('amazon_s3_bucket', ''); + } + + /** + * create transcoding job on Zencoder.com + */ + public function create($file) { + global $base_url; + // API Key + $api_key = variable_get('video_zencoder_api_key', ''); + // File details + $filename = $file->filename; + // Get varialbes + $bucket = $this->bucket; + $thumb_no = variable_get('video_thumbs', 5); + + $thumb_size = variable_get('video_thumbs_size', '160x120'); + $thumb_base = $baseurl; + $thumb_prefix = $filename; + // dimentions + $dimentions = explode('x', $file->dimentions); + + $notify_url = variable_get('zc_notify_url', ''); + $notify_email = variable_get('zc_notify_email', ''); + + // Job details + $input_name = $bucket . '/' . $filename; +// watchdog('zencoder', $input_name); + // thumbnails + // Setup our thmbnail path. + $video_thumb_path = variable_get('video_thumb_path', 'video_thumbs'); + $final_thumb_path = file_directory_path() . '/' . $video_thumb_path . '/' . $file->fid; + + // Notifications +// if(!empty($notify_url)) +// $notifications[] = array('format' => 'json', 'url' => 'http://123.231.58.90/drupal-6/postback/jobs'); +// $url = $base_url . '/postback/jobs'; + $url = 'http://123.231.63.138/postback/jobs'; + $notifications[] = array('format' => 'json', 'url' => $url); + + //get the presets + $presets = $file->presets; + $public = (variable_get('amazon_s3_private', FALSE)) ? 0 : 1; + + // construct the output array with the presets + $zc_outputs = array(); + foreach ($presets as $name => $preset) { + $zc_output = array(); + $lable = 'VIDEO_' . $name . '_' . $file->fid; + $width = $dimentions[0]; + $height = $dimentions[1]; + $quality = $preset['quality']; + $speed = $preset['speed']; + $upscale = $preset['upscale']; + $stretch = $preset['stretch']; + $frame_rate = $preset['frame_rate']; + $max_frame_rate = $preset['max_frame_rate']; + $keyframe_interval = $preset['keyframe_interval']; + $video_bitrate = $preset['video_bitrate']; + $bitrate_cap = $preset['bitrate_cap']; + $buffer_size = $preset['buffer_size']; + $h264_profile = $preset['h264_profile']; + $h264_level = $preset['h264_level']; + $skip_video = $preset['skip_video']; + $audio_codec = $preset['audio_codec']; + $audio_bitrate = $preset['audio_bitrate']; + $audio_channels = $preset['audio_channels']; + $audio_sample_rate = $preset['audio_sample_rate']; + $skip_audio = $preset['skip_audio']; + $start_clip = $preset['start_clip']; + $clip_length = $preset['clip_length']; + + if (!empty($lable)) + $zc_output['label'] = $lable; + if (!empty($bucket)) + $zc_output['url'] = 's3://' . $bucket . '/' . pathinfo($filename, PATHINFO_DIRNAME) . '/converted/' . pathinfo($filename, PATHINFO_FILENAME) . '.' . $preset['extension']; + if (!empty($public)) + $zc_output['public'] = $public; + if (!empty($width)) + $zc_output['width'] = $width; + if (!empty($height)) + $zc_output['height'] = $height; + if (!empty($quality)) + $zc_output['quality'] = $quality; + if (!empty($speed)) + $zc_output['speed'] = $speed; + if (!empty($upscale)) + $zc_output['upscale'] = $upscale; + if (!empty($frame_rate)) + $zc_output['frame_rate'] = $frame_rate; + if (!empty($max_frame_rate)) + $zc_output['max_frame_rate'] = $max_frame_rate; + if (!empty($keyframe_interval)) + $zc_output['keyframe_interval'] = $keyframe_interval; + if (!empty($video_bitrate)) + $zc_output['video_bitrate'] = $video_bitrate; + if (!empty($bitrate_cap)) + $zc_output['bitrate_cap'] = $bitrate_cap; + if (!empty($buffer_size)) + $zc_output['buffer_size'] = $buffer_size; + if (!empty($h264_profile)) + $zc_output['h264_profile'] = $h264_profile; + if (!empty($h264_level)) + $zc_output['h264_level'] = $h264_level; + if (!empty($skip_video)) + $zc_output['skip_video'] = $skip_video; + if (!empty($audio_codec)) + $zc_output['audio_codec'] = $audio_codec; + if (!empty($audio_bitrate)) + $zc_output['audio_bitrate'] = $audio_bitrate; + if (!empty($audio_channels)) + $zc_output['audio_channels'] = $audio_channels; + if (!empty($audio_sample_rate)) + $zc_output['audio_sample_rate'] = $audio_sample_rate; + if (!empty($skip_audio)) + $zc_output['skip_audio'] = $skip_audio; + if (!empty($start_clip)) + $zc_output['start_clip'] = $start_clip; + if (!empty($clip_length)) + $zc_output['clip_length'] = $clip_length; + + // thumbnails + $thumbnails['number'] = $thumb_no; + if (!empty($thumb_size)) + $thumbnails['thumb_size'] = $thumb_size; + if (!empty($bucket)) + $thumbnails['base_url'] = 's3://' . $bucket . '/' . $final_thumb_path; + if (!empty($file->fid)) + $thumbnails['prefix'] = $file->fid; + $zc_output['thumbnails'] = $thumbnails; + + //notifications + if (!empty($notify_email)) + $notifications[] = $notify_email; + $zc_output['notifications'] = $notifications; + $zc_outputs[] = $zc_output; + } + + + $encoding_job_json = array( +// 'test' => 1, +// 'download_connections' => -1, + 'api_key' => $this->access_key, + 'input' => 's3://' . $input_name, + 'outputs' => $zc_outputs + ); + +// print_r(($encoding_job_json)); +// exit; +// watchdog('zencoder', json_encode($encoding_job_json)); + + $encoding_job = new ZencoderJob(json_encode($encoding_job_json)); + // Check if it worked + if ($encoding_job->created) { +// watchdog('zencoder', serialize($encoding_job)); + return $encoding_job; + } else { + foreach ($encoding_job->errors as $error) { + // echo $error."\n"; + watchdog('zencoder', 'Zencoder reports some errors. !error', array('!error' => $error), WATCHDOG_ERROR); + } + return false; + } + } + + /* + * Updates the database after a successful transfer to amazon. + */ + + public function update($video) { + $result = db_query("UPDATE {video_zencoder} SET status=%d, completed=%d WHERE jobid=%d", + $video->output->state, time(), $video->job->id); + return $result; + } + + /* + * Verifies the existence of a file id, returns the row or false if none found. + */ + + public function load_job($jobid) { + $sql = db_query("SELECT * FROM {video_zencoder} WHERE jobid=%d", $jobid); + $row = db_fetch_object($sql); + return $row; + } + + /** + * Create Zencoder user account + */ + public function create_user($user) { + + $request = new ZencoderRequest( + 'https://app.zencoder.com/api/account', + false, // API key isn't needed for new account creation + array( + "terms_of_service" => "1", + "email" => $user->email, + "affiliate_code" => "drupal-video" + ) + ); + + if ($request->successful) { + $results = $request->results; + variable_set('video_zencoder_api_key', $results['api_key']); + $message = drupal_mail('video_zencoder', 'video_zencoder', $user->email, language_default(), $results); + if (!$message['result']) { + drupal_set_message(t('Unable to send e-mail!. Your Zencoder Details are as below.
API Key : !api_key
Password : !password
', array('!api_key' => $results['api_key'], '!password' => $results['password'])), 'status'); + } else { +// drupal_mail('video_zencoder', 'video_zencoder', 'heshanmw@gmail.com', language_default(), $results); + drupal_set_message(t('Your account has been created and is ready to start processing on Zencoder')); + } +// return $request->results; + return true; +// variable_set('video_zencoder_api_key', ''); + } else { + $errors = ''; + foreach ($request->errors as $error) { + if($error == 'Email has already been taken'){ + drupal_set_message(t('Your account already exists on Zencoder. So !login to here and enter API key below', array('!login' => l(t('login'), 'https://app.zencoder.com/session/new')))); + variable_set('video_zencoder_api_key', 'Please enter your API Key'); + return TRUE; + } + $errors .= $error; + } + return $errors; + } + } + +} \ No newline at end of file diff --git a/plugins/video_zencoder/transcoders/video_zencoder.inc b/plugins/video_zencoder/transcoders/video_zencoder.inc new file mode 100644 index 0000000..0979d8f --- /dev/null +++ b/plugins/video_zencoder/transcoders/video_zencoder.inc @@ -0,0 +1,382 @@ + $thumbfile, '%out' => $s3_get_object); + $error_msg = t("Error downloading thumbnail for video: generated file %file does not exist.
S3 Output:
%out", $error_param); + // Log the error message. + watchdog('zencoder', $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 = FILE_STATUS_TEMPORARY; + $file->filename = trim($filename); + $file->filepath = $thumbfile; + $file->filemime = file_get_mimetype($filename); + $file->filesize = filesize($thumbfile); + $file->timestamp = time(); + $files[] = $file; + } + return $files; + } + + public function video_converted_extension() { + return variable_get('video_zencoder_ext', 'flv'); + } + + public function convert_video($video) { + // get the active jobs and check for the status + if ($video->video_status == VIDEO_RENDERING_ACTIVE) { + return; + } + // We need to check if this file id exists in our S3 table to avoid file not found error. + $s3_video = db_query("SELECT * FROM {video_s3} WHERE fid=%d", $video->fid); + if ($s3_file = db_fetch_object($s3_video)) { + // This is a s3 file, lets verify it has been pushed and if so lets push to Zencoder queue. + if ($s3_file->status == VIDEO_S3_COMPLETE) { + $video_s3 = $s3_file; + } + } else { + watchdog('zencoder', t('You must active the Video S3 module to work with Zencoder, file is not found in S3 tables', array()), array(), WATCHDOG_ERROR); +// return FALSE; + } + + // If we have a video lets go ahead and send it. + if (is_object($video_s3)) { + // This will update our current video status to active. + $this->change_status($video->vid, VIDEO_RENDERING_ACTIVE); + // bucket name + $bucket = variable_get('amazon_s3_bucket', ''); + $ssl = variable_get('amazon_s3_ssl', FALSE); + + $filepath = $video_s3->filepath; + $video_s3->dimensions = $video->dimensions; + $filename = str_replace(' ', '_', $video_s3->filename); + $video_s3->presets = $video->presets; + module_load_include('inc', 'video_zencoder', '/includes/zencoder'); + $zc = new video_zencoder_api; + if ($encoding_job = $zc->create($video_s3)) { + // Update our table. + $video->vid = $video->vid; + //job id + $video->jobid = $encoding_job->id; + $outputs = new stdClass(); +// print_r($encoding_job->outputs); + foreach ($encoding_job->outputs as $output) { + $outputs->{$output->id}->id = $output->id; + $outputs->{$output->id}->label = $output->label; + $outputs->{$output->id}->url = $output->url; + $outputs->{$output->id}->state = $output->state; + $outputs->{$output->id}->error_message = $output->error_message; + $outputs->{$output->id}->error_link = $output->error_link; + } + $video->data = serialize($outputs); + // write output values to the table + if ($this->update($video)) { + watchdog('zencoder', t('Successfully created trancoding job on !jobid.', array('!jobid' => $video->jobid)), array(), WATCHDOG_INFO); + } + } else { + watchdog('zencoder', 'Failed to queus our file to Zencoder.', array(), WATCHDOG_ERROR); + $this->change_status($video->vid, VIDEO_RENDERING_FAILED); + return FALSE; + } + } else { + watchdog('zencoder', 'We did not find the file id: ' . $video->fid . ' or it is still queued for ffmpeg processing or S3 push.', array(), WATCHDOG_DEBUG); +// return FALSE; + } + } + + /** + * 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() { + $help = l(t('Zencoder'), 'http://zencoder.com/'); + return $help; + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/transcoder_interface#admin_settings() + */ + public function admin_settings() { + global $user; + // check amazon s3 module is exists or not + if (!module_exists('video_s3')) + drupal_set_message(t('You must enable Video Amazon S3 Module to enable this module.'), 'error'); + + $form = array(); + $form['video_zencoder_start'] = array( + '#type' => 'markup', + '#value' => '
', + ); + $zencoder_api = variable_get('video_zencoder_api_key', null); + if (!isset($zencoder_api) && empty($zencoder_api)) { + $form['zencoder_user'] = array( + '#type' => 'fieldset', + '#title' => t('Zencoder User'), + '#collapsible' => FALSE, + '#collapsed' => FALSE, + '#description' => t('Save configurations to creare your !link account to transcode and manage your videos using Zencode API. Once you save your configurations then this will automatically create an account on the Zencoder.com and password and all ther other relevent details will be emailled to you.', array('!link' => l(t('Zencoder.com'), 'http://zencoder.com'))) + ); + $form['zencoder_user']['zencoder_username'] = array( + '#type' => 'textfield', + '#title' => t('Your email address'), + '#default_value' => variable_get('zencoder_username', 'me@localhost'), + '#size' => 50, + '#description' => t('Make sure the email is accurate, since we will send all the password details to manage transcoding online and API key details to this.') + ); + + $form['zencoder_user']['agree_terms_zencoder'] = array( + '#type' => 'checkbox', + '#title' => t('Agree Zencoder Terms and Conditions.'), + '#default_value' => variable_get('agree_terms_zencoder', TRUE), + '#description' => t('Read terms and conditions on !link.', array('!link' => l(t('Zencoder.com'), 'http://zencoder.com'))), + ); + } else { + // Zencoder API is exists + $form['zencoder_info'] = array( + '#type' => 'fieldset', + '#title' => t('Zencoder API'), + '#collapsible' => FALSE, + '#collapsed' => FALSE, + ); + $form['zencoder_info']['video_zencoder_api_key'] = array( + '#type' => 'textfield', + '#title' => t('Zencoder API Key'), + '#default_value' => variable_get('video_zencoder_api_key', null), + '#description' => t('Zencoder API Key. Click Reset to default button to add new account.') + ); + $form['zencoder_info']['video_thumbs'] = array( + '#type' => 'textfield', + '#title' => t('Number of thumbnails'), + '#description' => t('Number of thumbnails to display from video.'), + '#default_value' => variable_get('video_thumbs', 5), + '#size' => 5 + ); + $form['zencoder_info']['video_thumbs_size'] = array( + '#type' => 'textfield', + '#title' => t('Dimention of thumbnails'), + '#description' => t('Size of thumbnails to extract from video.'), + '#default_value' => variable_get('video_thumbs_size', '160x120'), + '#size' => 10 + ); + } + $form['video_zencoder_end'] = array( + '#type' => 'markup', + '#value' => '
', + ); + return $form; + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/transcoder_interface#admin_settings_validate() + */ + public function admin_settings_validate($form, &$form_state) { + $zencoder_api = isset($form_state['values']['video_zencoder_api_key']) ? $form_state['values']['video_zencoder_api_key'] : NULL; + if (isset($zencoder_api) && !empty($zencoder_api) || $form_state['values']['vid_convertor'] != 'video_zencoder') { + if (variable_get('vid_filesystem', 'drupal') != 'video_s3') { + form_set_error('video_zencoder', t('You must enable the Amazon S3 as !link.', array('!link' => l(t('FileSystem'), 'admin/settings/video/filesystem')))); + } + return; + } + + $errors = false; + // check terms and condition + if ($form_state['values']['agree_terms_zencoder'] == 0) { + $errors = true; + form_set_error('agree_terms_zencoder', t('You must agree terms and conditions.', array())); + } + // check for email exists + // Validate the e-mail address: + if ($error = user_validate_mail($form_state['values']['zencoder_username'])) { + $errors = true; + form_set_error('zencoder_username', $error); + } + + // get the API key from zencoder and save it to variable + if (!$errors) { + $email = $form_state['values']['zencoder_username']; + module_load_include('inc', 'video_zencoder', '/includes/zencoder'); + $zc = new video_zencoder_api; +// if(!($error = $zc->create_user($user))) +// form_set_error('zencoder_username', $error); + $user = new stdClass; + $user->email = $email; + $result = $zc->create_user($user); + if ($result !== true) + form_set_error('zencoder_username', $result); + } + } + + /** + * Return the dimensions of a video + */ + public function get_dimensions($video) { + // @TODO get object properties + return; + } + + public function create_job($video) { + return db_query("INSERT INTO {video_zencoder} (fid, status, dimensions) VALUES (%d, %d, '%s')", $video['fid'], VIDEO_RENDERING_PENDING, $video['dimensions']); + } + + public function update_job($video) { + if (!$this->load_job($video['fid'])) + return; + //lets update our table to include the nid + db_query("UPDATE {video_zencoder} SET nid=%d WHERE fid=%d", $video['nid'], $video['fid']); + } + + public function delete_job($video) { + if (!$this->load_job($video->fid)) + return; + //lets get all our videos and unlink them + $sql = db_query("SELECT vid FROM {video_zencoder} WHERE fid=%d", $video->fid); + //we loop here as future development will include multiple video types (HTML 5) + while ($row = db_fetch_object($sql)) { + // @TODO : cancel the job to transcode + } + //now delete our rows. + db_query('DELETE FROM {video_zencoder} WHERE fid = %d', $video->fid); + } + + public function load_job($fid) { + $job = null; + $result = db_query('SELECT f.*, vf.vid, vf.nid, vf.dimensions, vf.status as video_status FROM {video_zencoder} vf LEFT JOIN {files} f ON vf.fid = f.fid WHERE f.fid=vf.fid AND f.fid = %d', $fid); + $job = db_fetch_object($result); + if (!empty($job)) + return $job; + else + return FALSE; + } + + public function load_job_queue() { + // load jobs with status as pending and active both + $total_videos = variable_get('video_instances', 5); + $videos = array(); + $result = db_query_range('SELECT f.*, vf.vid, vf.nid, vf.dimensions, vf.status as video_status FROM {video_zencoder} vf LEFT JOIN {files} f ON vf.fid = f.fid WHERE vf.status = %d AND f.status = %d ORDER BY f.timestamp', + VIDEO_RENDERING_PENDING, FILE_STATUS_PERMANENT, 0, $total_videos); + + while ($row = db_fetch_object($result)) { + $videos[] = $row; + } + return $videos; + } + + /** + * @todo : replace with the load job method + * @param $video + * @return + */ + public function load_completed_job(&$video) { + module_load_include('inc', 'video_s3', '/includes/amazon_s3'); + $video_row = db_fetch_object(db_query('SELECT data FROM {video_zencoder} WHERE fid = %d', $video->fid)); + $data = unserialize($video_row->data); + if(empty($data)) + return $video; + foreach ($data as $value) { + $extension = pathinfo($value->url, PATHINFO_EXTENSION); + $video->files->{$extension}->filename = pathinfo($value->url, PATHINFO_FILENAME) . '.' . $extension; + $video->files->{$extension}->filepath = $value->url; + $video->files->{$extension}->url = (!variable_get('amazon_s3_private', FALSE)) ? $value->url : video_s3_get_authenticated_url($video->filename); + $video->files->{$extension}->extension = $extension; + $video->player = strtolower($extension); + } + return $video; + } + + /** + * Change the status of the file. + * + * @param (int) $vid + * @param (int) $status + */ + public function change_status($vid, $status) { + $result = db_query('UPDATE {video_zencoder} SET status = %d WHERE vid = %d ', $status, $vid); + } + + /* + * Updates the database after a successful transfer to amazon. + */ + + private function update($video) { + $result = db_query("UPDATE {video_zencoder} SET jobid = %d, completed=%d, data='%s' WHERE vid=%d", + $video->jobid, time(), $video->data, $video->vid); + return $result; + } + +} \ No newline at end of file diff --git a/plugins/video_zencoder/video_zencoder.info b/plugins/video_zencoder/video_zencoder.info new file mode 100644 index 0000000..02b4ae6 --- /dev/null +++ b/plugins/video_zencoder/video_zencoder.info @@ -0,0 +1,9 @@ +;$Id$ + +name = Zencoder API on Video +description = Zencoder is a webservice to transcode videos. +package = "Video" +dependencies[] = video +dependencies[] = video_s3 +core = 6.x +version = 6.x-4.x-dev \ No newline at end of file diff --git a/plugins/video_zencoder/video_zencoder.install b/plugins/video_zencoder/video_zencoder.install new file mode 100644 index 0000000..a418815 --- /dev/null +++ b/plugins/video_zencoder/video_zencoder.install @@ -0,0 +1,135 @@ + t('Store video s3 cdn and convert with zencoder webservice'), + 'fields' => array( + 'vid' => array( + 'description' => t('Auto Increment id'), + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'fid' => array( + 'description' => t('Original file id'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'nid' => array( + 'description' => t('Node id'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'jobid' => array( + 'description' => t('Job id'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'status' => array( + 'description' => t('Status of the cdn transfer'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'dimensions' => array( + 'type' => 'varchar', + 'length' => '255', + 'default' => '', + 'description' => t('The dimensions of the video.'), + ), + 'completed' => array( + 'description' => t('Time of successful completion to amazon.'), + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'data' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + 'description' => 'A serialized array of converted files. Use of this field is discouraged and it will likely disappear in a future version of Drupal.', + ), + ), + 'indexes' => array( + 'status' => array('status'), + 'file' => array('fid'), + ), + 'primary key' => array('vid'), + ); + return $schema; +} + +/** + * Implementation of hook_install(). + */ +function video_zencoder_install() { + drupal_install_schema('video_zencoder'); + // set the module weight to low since we need this to load later time than in S3 + db_query("UPDATE {system} SET weight = 50 WHERE name = 'video_zencoder'"); +} + +/** + * Implementation of hook_uninstall(). + */ +function video_zencoder_uninstall() { + drupal_uninstall_schema('video_zencoder'); + // TODO : Delete our variables. +} + +// ALTER TABLE `video_zencoder` ADD `dimensions` VARCHAR( 255 ) NULL AFTER `filesize` ; +/** + * Update 6401 + * Adding new dimentaion row to the table + * @return + */ +function video_zencoder_update_6401() { + $ret = array(); + // set settings column to accept larger values + switch ($GLOBALS['db_type']) { + case 'mysql': + case 'mysqli': + $ret[] = update_sql('ALTER TABLE {video_zencoder} ADD dimensions VARCHAR( 255 ) NULL NULL AFTER filesize'); + break; + + case 'pgsql': + db_add_column($ret, 'video_zencoder', 'dimensions', 'VARCHAR', array('null' => TRUE)); + break; + } + + return $ret; +} + +/** + * Update 6402 + * Adding data field and remove unwanted fields from the table + * @return + */ +function video_zencoder_update_6402() { + $ret = array(); + // drop un wanted fields in video zencoder + db_drop_field($ret, 'video_zencoder', 'filesize'); + db_drop_field($ret, 'video_zencoder', 'outputid'); + db_drop_field($ret, 'video_zencoder', 'bucket'); + db_drop_field($ret, 'video_zencoder', 'filename'); + db_drop_field($ret, 'video_zencoder', 'filepath'); + db_drop_field($ret, 'video_zencoder', 'filemime'); + db_add_column($ret, 'video_zencoder', 'data', 'longtext', array('null' => TRUE)); + return $ret; +} diff --git a/plugins/video_zencoder/video_zencoder.module b/plugins/video_zencoder/video_zencoder.module new file mode 100644 index 0000000..f8a779b --- /dev/null +++ b/plugins/video_zencoder/video_zencoder.module @@ -0,0 +1,152 @@ + 'Video', +// 'description' => 'Configure different aspects of the video module and its plugins', + 'page callback' => '_video_zencoder_postback_jobs', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * This will handle Zencoder postback once video conversion is completed + * + */ +function _video_zencoder_postback_jobs() { +// get JSON post data + $data = file_get_contents("php://input"); + watchdog('zencoder', t('Postback received from the Zencoder Transcoding servers.' . serialize($data))); +// print_r($data); +// exit; +// get the file object by zenocder job id + $video = json_decode($data); +// print_r($zc); +// $zc_job_id = $video->job->id; + $zc_job_state = trim($video->job->state); +// $zc_output_id = $video->output->id; + $zc_output_state = trim($video->output->state); +// $zc_output_url = $video->output->url; + if ($zc_output_state == 'finished' && $zc_job_state == 'finished') + $video->output->state = VIDEO_RENDERING_COMPLETE; + if ($zc_output_state == 'failed' || $zc_job_state == 'failed') + $video->output->state = VIDEO_RENDERING_FAILED; + if ($zc_job_state == 'processing') { + watchdog('zencoder', t('Job !jobid is processing.', array('!jobid' => $video->job->id))); + return; + } + +// update the Zencoder Job + module_load_include('inc', 'video_zencoder', '/includes/zencoder'); + $zc = new video_zencoder_api; +// Lets run delete. + $videodb = $zc->load_job($video->job->id); +// print_r($video); +// echo $nid = $videodb->nid; +// echo $vid = $videodb->vid; +// echo $fid = $videodb->fid; + + if ($video->output->state == VIDEO_RENDERING_COMPLETE) { +// echo 'completed'; + $nid = $videodb->nid; + $vid = $videodb->vid; + $fid = $videodb->fid; +// print_r($videodb); +// echo 'working completed'; + if (!db_query('UPDATE {video_zencoder} SET status = %d WHERE vid = %d ', VIDEO_RENDERING_COMPLETE, $vid)) + watchdog('zencoder', t('Error updating zencoder status.', array()), NULL, WATCHDOG_ERROR); + + if (!db_query("UPDATE {node} SET status=%d WHERE nid=%d", 1, $nid)) + watchdog('zencoder', t('Error pulishing node.', array()), NULL, WATCHDOG_ERROR); + +// print_r($video); +// update the thumbanils +// this will update the default thumbnails, if user want to select another one then they wil need to edit the node +// Setup our thmbnail path. + $video_thumb_path = variable_get('video_thumb_path', 'video_thumbs'); + $final_thumb_path = file_directory_path() . '/' . $video_thumb_path . '/' . $fid; +// $i = rand(0, (variable_get('no_of_video_thumbs', 5) - 1)); + $filename = $fid . '_' . sprintf("%04d", 1) . '.png'; + $thumbfile = $final_thumb_path . '/' . $filename; + + if (video_s3_get_object_info($thumbfile)) { + $default = $final_thumb_path . '/no-thumb.png'; + file_delete($default); + if (video_s3_get_object($thumbfile, $default)) + watchdog('zencoder', t('Successfully downloaded the thumbnails file and replaced the default image.')); + else + watchdog('zencoder', t('Download thumbanils files is failed.'), array(), WATCHDOG_ERROR); + } +// file_copy($default, $thumbfile, FILE_EXISTS_REPLACE); + watchdog('zencoder', t('Updated the Zencoder Job !id to states !states.', array('!id' => $video->job->id, '!states' => $zc_output_state))); + } + else if ($video->output->state == VIDEO_RENDERING_FAILED) { + echo 'working failed'; + $this->change_status($vid, VIDEO_RENDERING_FAILED); + watchdog('zencoder', t('Zencoder job failed converting videos, please login to zencoder web and check the erros.', array()), NULL, WATCHDOG_ERROR); + } else { + echo 'working something else'; + } +} + +/** + * Implementation of hook_mail(). + */ +function video_zencoder_mail($key, &$message, $params) { + $language = $message['language']; + $message['subject'] .= 'Zencoder Registration Details for Drupal Video'; + $message['body'][] = video_zencoder_mail_default($params); +} + +function video_zencoder_mail_default($params) { + return t( + 'Welcome to Zencoder for Drupal +------------------------------- + +Your account has been created and is ready to start processing. + +Your account details are as below. + +API Key : %api_key +Password : %password + +* Login URL: https://app.zencoder.com/login + +You can get help at the following places: + +* Our chat room at http://zencoder.com/chat +* Customer forums at https://help.zencoder.com/forums +* The help desk at https://help.zencoder.com/tickets/new + +We\'d love to hear from you. Let us know how we can help. Thanks! + +Thanks, +-Zencoder for Drupal Team', array('%api_key' => $params['api_key'], '%password' => $params['password'])); +} + diff --git a/theme/.cvsignore b/theme/.cvsignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/theme/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/theme/video-play-dcr.tpl.php b/theme/video-play-dcr.tpl.php new file mode 100644 index 0000000..dd9f86d --- /dev/null +++ b/theme/video-play-dcr.tpl.php @@ -0,0 +1,17 @@ + + + + + l(t('Plugin'), 'http://www.macromedia.com/shockwave/download/'))); ?> + + \ No newline at end of file diff --git a/theme/video-play-divx.tpl.php b/theme/video-play-divx.tpl.php new file mode 100644 index 0000000..3148efa --- /dev/null +++ b/theme/video-play-divx.tpl.php @@ -0,0 +1,26 @@ + + + + + + + + + + l(t('Plugin'), 'http://go.divx.com/plugin/download/'))); ?> + + \ No newline at end of file diff --git a/theme/video-play-flash.tpl.php b/theme/video-play-flash.tpl.php new file mode 100644 index 0000000..264578c --- /dev/null +++ b/theme/video-play-flash.tpl.php @@ -0,0 +1,20 @@ + + + + + + + l(t('Plugin'), 'http://get.adobe.com/flashplayer/'))); ?> + + \ No newline at end of file diff --git a/theme/video-play-flv.tpl.php b/theme/video-play-flv.tpl.php new file mode 100644 index 0000000..661ad04 --- /dev/null +++ b/theme/video-play-flv.tpl.php @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/theme/video-play-html5.tpl.php b/theme/video-play-html5.tpl.php new file mode 100644 index 0000000..8f97650 --- /dev/null +++ b/theme/video-play-html5.tpl.php @@ -0,0 +1,32 @@ + + + \ No newline at end of file diff --git a/theme/video-play-quicktime.tpl.php b/theme/video-play-quicktime.tpl.php new file mode 100644 index 0000000..a69ce26 --- /dev/null +++ b/theme/video-play-quicktime.tpl.php @@ -0,0 +1,32 @@ + + * + */ +?> + + + + + + + + + \ No newline at end of file diff --git a/theme/video-play-realmedia.tpl.php b/theme/video-play-realmedia.tpl.php new file mode 100644 index 0000000..738137f --- /dev/null +++ b/theme/video-play-realmedia.tpl.php @@ -0,0 +1,22 @@ + + + + + + + + + l(t('Plugin'), 'http://www.real.com/realplayer'))); ?> + + \ No newline at end of file diff --git a/theme/video-play-theora.tpl.php b/theme/video-play-theora.tpl.php new file mode 100644 index 0000000..a943d13 --- /dev/null +++ b/theme/video-play-theora.tpl.php @@ -0,0 +1,23 @@ + + + + + + + + + + + l(t('Plugin'), 'http://www.theora.org/cortado/'))); ?> + \ No newline at end of file diff --git a/theme/video-play-windowsmedia.tpl.php b/theme/video-play-windowsmedia.tpl.php new file mode 100644 index 0000000..df0838c --- /dev/null +++ b/theme/video-play-windowsmedia.tpl.php @@ -0,0 +1,21 @@ + + + + + + + + + l(t('Plugin'), 'http://www.microsoft.com/windows/windowsmedia/player/download/'))); ?> + \ No newline at end of file diff --git a/transcoders/.cvsignore b/transcoders/.cvsignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/transcoders/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/transcoders/video_ffmpeg.inc b/transcoders/video_ffmpeg.inc new file mode 100644 index 0000000..ebdd664 --- /dev/null +++ b/transcoders/video_ffmpeg.inc @@ -0,0 +1,551 @@ +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['command'] = variable_get('video_ffmpeg_helper_auto_cvr_options', $this->command); + $this->params['cmd_path'] = variable_get('video_transcoder_path', $this->ffmpeg); + $this->params['thumb_command'] = variable_get('video_ffmpeg_thumbnailer_options', $this->thumb_command); + $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'); + } + + public function run_command($options) { +// $command = $this->nice . ' ' . $this->params['cmd_path'] . ' ' . $options . ' 2>&1'; + $command = $options . ' 2>&1'; + watchdog('video_ffmpeg', 'Executing command: ' . $command, array(), WATCHDOG_DEBUG); + ob_start(); + passthru($command, $command_return); + $output = ob_get_contents(); + ob_end_clean(); + return $output; + } + + public function generate_thumbnails($video) { + global $user; + // Setup our thmbnail path. + $video_thumb_path = variable_get('video_thumb_path', 'video_thumbs'); + $final_thumb_path = file_directory_path() . '/' . $video_thumb_path . '/' . $video['fid']; + + // Ensure the destination directory exists and is writable. + $directories = explode('/', $final_thumb_path); + // Get the file system directory. + $file_system = file_directory_path(); + foreach ($directories as $directory) { + $full_path = isset($full_path) ? $full_path . '/' . $directory : $directory; + // Don't check directories outside the file system path. + if (strpos($full_path, $file_system) === 0) { + field_file_check_directory($full_path, FILE_CREATE_DIRECTORY); + } + } + + // Total thumbs to generate + $total_thumbs = variable_get('video_thumbs', 5); + $videofile = escapeshellarg($video['filepath']); + //get the playtime from the current transcoder + $duration = $this->get_playtime($video['filepath']); + + $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 = "/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)) { + //setup the command to be passed to the transcoder. + $options = $this->params['cmd_path'] . ' ' . t($this->params['thumb_command'], array('!videofile' => $videofile, '!seek' => $seek, '!thumbfile' => $thumbfile)); + // Generate the thumbnail from the video. + $command_output = $this->run_command($options); + if (!file_exists($thumbfile)) { + $error_param = array('%file' => $thumbfile, '%cmd' => $options, '%out' => $command_output); + $error_msg = t("Error generating thumbnail for video: generated file %file does not exist.
Command Executed:
%cmd
Command Output:
%out", $error_param); + // Log the error message. + watchdog('video_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 = FILE_STATUS_TEMPORARY; + $file->filename = trim($filename); + $file->filepath = $thumbfile; + $file->filemime = file_get_mimetype($filename); + $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 +// $pathinfo = pathinfo($video->filepath); + // @TODO This about getting the correct path from the filefield if they active it + $files = file_create_path(); + $original = $files . '/videos/original'; + $converted = $files . '/videos/converted'; + + if (!field_file_check_directory($original, FILE_CREATE_DIRECTORY)) { + watchdog('video_transcoder', 'Video conversion failed. Could not create the directory: ' . $orginal, array(), WATCHDOG_ERROR); + return false; + } + if (!field_file_check_directory($converted, FILE_CREATE_DIRECTORY)) { + watchdog('video_transcoder', 'Video conversion failed. Could not create the directory: ' . $converted, array(), WATCHDOG_ERROR); + return false; + } + + $original = $original . '/' . $video->filename; + //lets move our video and then convert it. + if (file_move($video, $original)) { + // Update our filepath since we moved it + $update = drupal_write_record('files', $video, 'fid'); + // process presets + $presets = $video->presets; + $converted_files = array(); + foreach ($presets as $name => $preset) { + // reset converted file path + $converted = $files . '/videos/converted'; + //update our filename after the move to maintain filename uniqueness. +// $converted = $converted .'/'. pathinfo($video->filepath, PATHINFO_FILENAME) .'.'. $this->video_extension(); + $converted = file_create_filename(str_replace(' ', '_', pathinfo($video->filepath, PATHINFO_FILENAME)) . '.' . $preset['extension'], $converted); + //call our transcoder +// $command_output = $this->convert_video($video, $converted); + $dimensions = $this->dimensions($video); + $dimention = explode('x', $dimensions); + if ($this->params['enable_faststart'] && in_array($preset['extension'], array('mov', 'mp4'))) { + $ffmpeg_output = file_directory_temp() . '/' . basename($converted); + } else { + $ffmpeg_output = $converted; + } + // Setup our default command to be run. + foreach ($preset['command'] as $command) { + $command = strtr($command, array( + '!cmd_path' => $this->params['cmd_path'], + '!videofile' => '"' . $video->filepath . '"', + '!audiobitrate' => $preset['audio_bitrate'], + '!width' => $dimention[0], + '!height' => $dimention[1], + '!videobitrate' => $preset['video_bitrate'], + '!convertfile' => '"' . $ffmpeg_output . '"', + )); +// print_r($preset['command']); +// die(); + // Process our video +// $command_output = $this->run_command($command); + $command_output = $this->run_command($command); + } + + 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. + $cmd_path = $this->params['cmd_path']; + $this->params['cmd_path'] = $this->params['faststart_cmd']; + $command_output .= $this->run_command($ffmpeg_output . ' ' . $converted, $verbose); + $this->params['cmd_path'] = $cmd_path; + + // 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($converted)) { + watchdog('video_conversion', 'Video conversion failed for preset %preset. FFMPEG reported the following output: ' . $command_output, array('%orig' => $video->filepath, '%preset' => $name), WATCHDOG_ERROR); + $this->change_status($video->vid, VIDEO_RENDERING_FAILED); + return FALSE; + } + // Setup our converted video object + $video_info = pathinfo($converted); + //update our converted video + $video->converted = new stdClass(); + $video->converted->vid = $video->vid; + $video->converted->filename = $video_info['basename']; + $video->converted->filepath = $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. + $result = db_query("UPDATE {video_files} SET status=%d, completed=%d, data='%s' WHERE vid=%d", + $video->converted->status, $video->converted->completed, serialize($converted_files), $video->converted->vid); + + watchdog('video_conversion', 'Successfully converted %orig to %dest', array('%orig' => $video->filepath, '%dest' => $video->converted->filepath), WATCHDOG_INFO); + return TRUE; + } else { + watchdog('video_conversion', 'Cound not move the video to the original folder.', array(), WATCHDOG_ERROR); + $this->change_status($video->vid, VIDEO_RENDERING_FAILED); + return FALSE; + } + } + + /** + * Get some information from the video file + */ + public function get_video_info($video) { + static $command_ouput; + if (!empty($command_output)) + return $command_output; + + $file = escapeshellarg($video); + // Execute the command + $options = $this->params['cmd_path'] . ' -i ' . $file; + $command_output = $this->run_command($options); + return $command_output; + } + + /** + * 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' => '
', + ); + + $form['video_transcoder_path'] = array( + '#type' => 'textfield', + '#title' => t('Path to Video Transcoder'), + '#description' => t('Absolute path to ffmpeg.'), + '#default_value' => variable_get('video_transcoder_path', '/usr/bin/ffmpeg'), + ); + $form['video_thumbs'] = array( + '#type' => 'textfield', + '#title' => t('Number of thumbnails'), + '#description' => t('Number of thumbnails to display from video.'), + '#default_value' => variable_get('video_thumbs', 5), + ); + $form['video_ffmpeg_nice_enable'] = array( + '#type' => 'checkbox', + '#title' => t('Enable the use of nice when calling the ffmpeg command.'), + '#default_value' => variable_get('video_ffmpeg_nice_enable', TRUE), + '#description' => t('The nice command Invokes a command with an altered scheduling priority. This option may not be available on windows machines, so disable it.') + ); + // Thumbnail Videos We need to put this stuff last. + $form['autothumb'] = array( + '#type' => 'fieldset', + '#title' => t('Video Thumbnails'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['autothumb']['video_thumb_path'] = array( + '#type' => 'textfield', + '#title' => t('Path to save thumbnails'), + '#description' => t('Path to save video thumbnails extracted from the videos.'), + '#default_value' => variable_get('video_thumb_path', 'video_thumbs'), + ); + $form['autothumb']['advanced'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced Settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE + ); + $form['autothumb']['advanced']['video_ffmpeg_thumbnailer_options'] = array( + '#type' => 'textarea', + '#title' => t('Video thumbnailer options'), + '#description' => t('Provide the options for the thumbnailer. Available argument values are: ') . '
  1. ' . t('!videofile (the video file to thumbnail)') . '
  2. ' . t('!thumbfile (a newly created temporary file to overwrite with the thumbnail)
'), + '#default_value' => variable_get('video_ffmpeg_thumbnailer_options', '-i !videofile -an -y -f mjpeg -ss !seek -vframes 1 !thumbfile'), + ); + + // Video conversion settings. + $form['autoconv'] = array( + '#type' => 'fieldset', + '#title' => t('Video Conversion'), + '#collapsible' => TRUE, + '#collapsed' => TRUE + ); + $form['autoconv']['video_ffmpeg_enable_faststart'] = array( + '#type' => 'checkbox', + '#title' => t('Process mov/mp4 videos with qt-faststart'), + '#default_value' => variable_get('video_ffmpeg_enable_faststart', 0), + ); + $form['autoconv']['video_ffmpeg_faststart_cmd'] = array( + '#type' => 'textfield', + '#title' => t('Path to qt-faststart'), + '#default_value' => variable_get('video_ffmpeg_faststart_cmd', '/usr/bin/qt-faststart'), + ); + + $form['autoconv']['video_ffmpeg_pad_method'] = array( + '#type' => 'radios', + '#title' => t('FFMPeg Padding method'), + '#default_value' => variable_get('video_ffmpeg_pad_method', 0), + '#options' => array( + 0 => t('Use -padtop, -padbottom, -padleft, -padright for padding'), + 1 => t('Use -vf "pad:w:h:x:y:c" for padding'), + ), + ); + + $form['video_ffmpeg_end'] = array( + '#type' => 'markup', + '#value' => '
', + ); + return $form; + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/transcoder_interface#admin_settings_validate() + */ + public function admin_settings_validate($form, &$form_state) { + return; + } + + public function create_job($video) { + return db_query("INSERT INTO {video_files} (fid, status, dimensions) VALUES (%d, %d, '%s')", $video['fid'], VIDEO_RENDERING_PENDING, $video['dimensions']); + } + + public function update_job($video) { + if (!$this->load_job($video['fid'])) + return; + //lets update our table to include the nid + db_query("UPDATE {video_files} SET nid=%d WHERE fid=%d", $video['nid'], $video['fid']); + } + + public function delete_job($video) { + if (!$this->load_job($video->fid)) + return; + //lets get all our videos and unlink them + $sql = db_query("SELECT data FROM {video_files} WHERE fid=%d", $video->fid); + //we loop here as future development will include multiple video types (HTML 5) + while ($row = db_fetch_object($sql)) { + $data = unserialize($row->data); + if (empty($data)) + continue; + foreach ($data as $file) { + if (file_exists($file->filepath)) + unlink($file->filepath); + } + } + //now delete our rows. + db_query('DELETE FROM {video_files} WHERE fid = %d', $video->fid); + } + + public function load_job($fid) { + $job = null; + $result = db_query('SELECT f.*, vf.vid, vf.nid, vf.dimensions, vf.status as video_status FROM {video_files} vf LEFT JOIN {files} f ON vf.fid = f.fid WHERE f.fid=vf.fid AND f.fid = %d', $fid); + $job = db_fetch_object($result); + if (!empty($job)) + return $job; + else + return FALSE; + } + + 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 {files} f ON vf.fid = f.fid WHERE vf.status = %d AND f.status = %d ORDER BY f.timestamp', + VIDEO_RENDERING_PENDING, FILE_STATUS_PERMANENT, 0, $total_videos); + + while ($row = db_fetch_object($result)) { + $videos[] = $row; + } + return $videos; + } + + /** + * @todo : replace with the load job method + * @param $video + * @return + */ + public function load_completed_job(&$video) { + $result = db_fetch_object(db_query('SELECT * FROM {video_files} WHERE fid = %d', $video->fid)); + $data = unserialize($result->data); + if (empty($data)) + return $video; + foreach ($data as $value) { + $extension = pathinfo($value->filepath, PATHINFO_EXTENSION); + $video->files->{$extension}->filename = pathinfo($value->filepath, PATHINFO_FILENAME) . '.' . $extension; + $video->files->{$extension}->filepath = $value->filepath; + $video->files->{$extension}->url = file_create_url($value->filepath); + $video->files->{$extension}->extension = $extension; + $video->player = strtolower($extension); + } + return $video; + } + + /** + * Change the status of the file. + * + * @param (int) $vid + * @param (int) $status + */ + public function change_status($vid, $status) { + $result = db_query('UPDATE {video_files} SET status = %d WHERE vid = %d ', $status, $vid); + } + + /* + * 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($video->filepath); + $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; + } + } + +} + +?> diff --git a/translations/.cvsignore b/translations/.cvsignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/translations/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/translations/de.po b/translations/de.po new file mode 100644 index 0000000..b6ae698 --- /dev/null +++ b/translations/de.po @@ -0,0 +1,560 @@ +# $Id$ +# +# German translation of Drupal (general) +# Copyright YEAR NAME +# Generated from files: +# video_render.php,v 1.1.2.7 2010/01/08 00:25:24 heshanmw +# video_scheduler.php,v 1.1.2.5 2009/12/25 08:35:25 heshanmw +# video.admin.inc,v 1.1.2.11 2010/01/08 00:05:37 heshanmw +# video.module,v 1.69.4.17.2.7 2009/12/23 18:01:58 heshanmw +# video.info,v 1.3.4.1.4.1 2009/12/13 12:59:22 heshanmw +# uploadfield.module,v 1.1.2.23 2010/01/09 15:18:55 heshanmw +# uploadfield.info,v 1.1.2.2 2009/12/23 11:16:59 heshanmw +# video.install,v 1.4.4.1.4.6 2009/12/24 12:35:03 heshanmw +# apiclient.inc,v 1.2.4.4 2009/11/13 14:39:03 heshanmw +# common.inc,v 1.2.4.10.2.9 2009/12/27 06:46:26 heshanmw +# ffmpeg.inc,v 1.1.2.12 2010/01/08 00:35:04 heshanmw +# uploadfield_convert.inc,v 1.1.2.12 2009/12/24 16:16:22 heshanmw +# uploadfield_file.inc,v 1.1.2.5 2009/12/23 13:46:09 heshanmw +# uploadfield_formatter.inc,v 1.1.2.11 2009/12/24 16:34:39 heshanmw +# uploadfield_thumb.inc,v 1.1.2.4 2009/12/24 16:16:22 heshanmw +# uploadfield_widget.inc,v 1.1.2.32 2010/01/17 02:46:46 heshanmw +# +msgid "" +msgstr "" +"Project-Id-Version: Drupal Video 3.6\n" +"POT-Creation-Date: 2010-02-14 16:02+0100\n" +"PO-Revision-Date: 2010-02-14 18:48+0100\n" +"Last-Translator: rastatt \n" +"Language-Team: Frank Tartler \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Poedit-Language: German\n" +"X-Poedit-Country: GERMANY\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: video_render.php:56;82;91;113;127 +#: video_scheduler.php:132;141;165;179 +msgid "video_render" +msgstr "" + +#: video_render.php:56 +msgid "Incorrect parameters to the video_render.php script." +msgstr "Fehlerhafte Parameter für as video_render.php-Skript." + +#: video_render.php:82 +#: video_scheduler.php:132 +msgid "video_render.php has been called with an invalid job resource. exiting." +msgstr "video_render.php wurde mit einer ungültigen Job-Ressource aufgerufen; wird beendet." + +#: video_render.php:91 +#: video_scheduler.php:141 +msgid "converted file is an empty file." +msgstr "Die konvertierte Datei ist eine leere Datei." + +#: video_render.php:113 +#: video_scheduler.php:165 +msgid "successfully converted %orig to %dest" +msgstr "%orig erfolgreich in %dest konvertiert" + +#: video_render.php:127 +#: video_scheduler.php:179 +msgid "error moving video %vid_file with nid = %nid to %dir the final directory. Check folder permissions.
The script was run by %uname .
The folder owner is %fowner .
The folder permissions are %perms ." +msgstr "Fehler beim Verschieben des Videos %vid_file (nid = %nid) ins finale Verzeichnis %dir. Die Ordner-Berechtigungen sollten geprüft werden.
. Das Skript wurde durch %uname ausgeführt.
Der Eigentümer des Ordners ist %fowner.
Die Ordner-Berechtigungen sind %perms." + +#: video_scheduler.php:80 +msgid "video_scheduler" +msgstr "" + +#: video_scheduler.php:80 +msgid "no video conversion jobs to schedule." +msgstr "Es stehen keine Jobs zur Video-Konverierung an." + +#: video.admin.inc:22 +msgid "General Behavior" +msgstr "Allgemeines Verhalten" + +#: video.admin.inc:53 +msgid "Automatically start video on page load" +msgstr "Video autmomatisch beim Laden der Seite starten" + +#: video.admin.inc:55 +msgid "Start the video when the page and video loads" +msgstr "Video starten wenn die Seite und das Video geladen wird" + +#: video.admin.inc:60 +msgid "Automatically start video buffering" +msgstr "Video automatisch beim Laden der Seite puffern" + +#: video.admin.inc:62 +msgid "Start buffering video when the page and video loads" +msgstr "Das Puffern des Videos starten wenn die Seite und das Video geladen wird" + +#: video.admin.inc:67 +msgid "Video Extra Players" +msgstr "Video-Extra-Player" + +#: video.admin.inc:73 +msgid "Path to OGG Cortado Player" +msgstr "Pfad zum OGG-Cortado-Player" + +#: video.admin.inc:75 +msgid "Copy your cortado.jar file to Drupal root and keep the setting un-changed." +msgstr "Die Datei cortado.jar in das Drupal-Stammverzeichnis kopieren, Einstellungen unverändert lassen." + +#: video.admin.inc:80 +msgid "Play HQ MP4 files in Flash Player" +msgstr "HQ-MP4-Dateien im Flash-Player abspielen" + +#: video.admin.inc:82 +msgid "Play HQ MP4 files in Flash player." +msgstr "HQ-MP4-Dateien im Flash-Player abspielen." + +#: video.admin.inc:99 +msgid "Video Additions" +msgstr "Video-Zusätze" + +#: video.admin.inc:106 +msgid "Video transcoder" +msgstr "Video-Transcoder" + +#: video.admin.inc:109 +msgid "Video transcoder will help you to video conversion and automatic thumbnail generaion. You must install !ffmpeg_wrapper module to enable ffmpeg_wrapper support" +msgstr "Der Video-Transcoder hilft bei der Video-Konvertierung und beim automatischen Erzeugen von Miniaturansichten. Das !ffmpeg_wrapper-Modul muss für die ffmpeg_wrapper-Unterstützung installiert werden" + +#: video.admin.inc:113 +msgid "Path to Video Transcoder" +msgstr "Pfad zum Video-Transcoder" + +#: video.admin.inc:114 +msgid "Path to executable, you can skip this if your usign ffmpeg_wrapper module support." +msgstr "Pfad zur ausführbaren Datei, dies kann übersprungen werden, wenn Unterstützung für das ffmpeg_wrapper-Modul verwendet wird." + +#: video.admin.inc:122 +msgid "Automatic video thumbnailing" +msgstr "Automatisches Erzeugen von Miniaturansichten für Videos" + +#: video.admin.inc:128 +msgid "Path to Video Thumbnails" +msgstr "Pfad zu den Video-Miniaturansichten" + +#: video.admin.inc:129 +msgid "Path to save video thumbanils extracted from video" +msgstr "Pfad in dem aus dem Video extrahierte Video-Miniaturansichten gespeichert werden sollen" + +#: video.admin.inc:134 +msgid "No of thumbnails" +msgstr "Anzahl Miniaturansichten" + +#: video.admin.inc:135 +msgid "No of thumbnails extracting from video" +msgstr "Anzahl der aus dem Video zu exxtrahierenden Miniaturansichten" + +#: video.admin.inc:140;191 +msgid "Advanced settings" +msgstr "Erweiterte Einstellungen" + +#: video.admin.inc:146 +msgid "Video thumbnailer options" +msgstr "Optionen für Video-Miniaturansichten" + +#: video.admin.inc:147 +msgid "Provide the options for the thumbnailer. Available argument values are: " +msgstr "Bitte die Optionen für das Erstellen von Miniaturansichten angeben. Verfügbare Argument-Werte sind:" + +#: video.admin.inc:147 +msgid "%videofile (the video file to thumbnail)" +msgstr "%videofile (das Video für das Miniaturansichten erstellt werden sollen)" + +#: video.admin.inc:147 +msgid "%thumbfile (a newly created temporary file to overwrite with the thumbnail)" +msgstr "%thumbfile (eine neu erstellte temporäre Datei, die mit der Miniaturansicht überschrieben werden soll)" + +#: video.admin.inc:147 +msgid "Only the first two are mandatory. For example, older versions of ffmpeg should use something like: !old While newer versions should use something like: !new" +msgstr "Nur die ersten beiden sind zwingend. Ältere Versionen von ffmpeg sollten z.B. etwas wie !old verwenden. Dagegen sollten neuere Versionen etwas wie !new verwenden." + +#: video.admin.inc:155 +msgid "Automatic video conversion" +msgstr "Automatische Video-Konvertierung" + +#: video.admin.inc:161 +msgid "Destination video Width" +msgstr "Breite des Ziel-Videos" + +#: video.admin.inc:168 +msgid "Destination video height" +msgstr "Höhe des Ziel-Videos" + +#: video.admin.inc:175 +msgid "Video bitrate" +msgstr "Video-Bitrate" + +#: video.admin.inc:176 +msgid "The video bitrate in bit/s of the converted video." +msgstr "Die Video-Bitrate in bit/s des konvertierten Videos." + +#: video.admin.inc:183 +msgid "Audio bitrate" +msgstr "Audio-Bitrate" + +#: video.admin.inc:184 +msgid "The audio bitrate in bit/s of the converted video." +msgstr "Die Audio-Bitrate in bit/s des konvertierten Videos." + +#: video.admin.inc:197 +msgid "Video converter options" +msgstr "Video-Konverter-Optionen" + +#: video.admin.inc:198 +msgid "Provide the ffmpeg options to configure the video conversion. Available argument values are: " +msgstr "Zu konfigurierende ffmpeg-Optionen für die Video-Konvertierung. Verfügbare Argumente sind:" + +#: video.admin.inc:199 +msgid "%videofile (the video file to convert)" +msgstr "%videofile (die zu konvertierenden Videodatei)" + +#: video.admin.inc:200 +msgid "%convertfile (a newly created file to store the converted file)" +msgstr "%convertfile (eine neu erstellte Datei in der das konvertierte Video geespeichert wird)" + +#: video.admin.inc:201 +msgid "%size (video resolution of the converted file)" +msgstr "%size (Video-Auflösung der konvertierten Datei)" + +#: video.admin.inc:203 +msgid "For further informations refer to the !ffmpegdoc" +msgstr "Die !ffmpegdoc enthält weitere Informationen." + +#: video.admin.inc:203 +msgid "Official FFMpeg documentation." +msgstr "offizielle FFMpeg-Dokumentation" + +#: video.admin.inc:220 +msgid "You do not have installed the !ffmpeg_wrapper module to enable using its plugin, please install it." +msgstr "Das ffmpeg_wrapper-Modul ist nicht installiert, daher kann dieses Plugin nicht aktiviert werden. Bitte installieren!" + +#: video.module:16 +msgid "v_perm" +msgstr "" + +#: video.module:25 +#: video.info:0;0 +#: types/uploadfield/uploadfield.module:201 +#: types/uploadfield/uploadfield.info:0 +msgid "Video" +msgstr "" + +#: video.module:26 +msgid "Configure different aspects of the video module and its plugins" +msgstr "Verschiedene Aspekte des Video-Moduls und seiner Plugins konfigurieren" + +#: video.install:17 +msgid "Store video transcoding queue" +msgstr "Warteschlange für Video-Transkodierung speichern" + +#: video.install:20 +msgid "original file id" +msgstr "Original-Datei-ID" + +#: video.install:27 +msgid "Process ID" +msgstr "Prozess-ID" + +#: video.install:34 +msgid "status of the transcoding" +msgstr "Status der Transkodierung" + +#: video.install:41 +msgid "Started transcodings" +msgstr "Transkodierungen gestartet" + +#: video.install:47 +msgid "Transcoding completed" +msgstr "Transkodierung abgeschlossen" + +#: video.install:53 +msgid "Informations related to the videos" +msgstr "Informationen in Bezug auf die Videos" + +#: video.info:0 +msgid "Allows video nodes." +msgstr "Video-Beiträge erlauben" + +#: includes/apiclient.inc:44;81 +msgid "YouTube Uploads not currently available" +msgstr "YouTube-Uploads sind momentan nicht verfügbar" + +#: includes/apiclient.inc:62;67 +msgid "YouTube uploads currently unavailable" +msgstr "YouTube-Uploads momentan nicht verfügbar" + +#: includes/apiclient.inc:33;40;63;68;80 +msgid "video_upload" +msgstr "" + +#: includes/apiclient.inc:33 +msgid "No YouTube username set" +msgstr "Kein YouTube-Benutzername angegeben" + +#: includes/apiclient.inc:40 +msgid "No YouTube password set" +msgstr "Kein YouTube-Passwort angegeben" + +#: includes/apiclient.inc:63 +msgid "Authentication error for YouTube Account" +msgstr "Authentifizierungsfehler mit den YouTube-Benutzerdaten" + +#: includes/apiclient.inc:68 +msgid "Authentication error for YouTube Account: %error" +msgstr "Authentifizierungsfehler mit den YouTube-Benutzerdaten: %errror" + +#: includes/apiclient.inc:80 +msgid "No developer key set" +msgstr "Kein Entwickler-Schlüssel angegeben" + +#: includes/apiclient.inc:104;133 +msgid "youtube_video" +msgstr "" + +#: includes/apiclient.inc:104 +msgid "Authentication error while creating a YouTube connection object: %error" +msgstr "Authentifizierungsfehler beim Erstellen des YouTube-Verbindungsobjekts: %error" + +#: includes/apiclient.inc:133 +msgid "Authentication error while receiving YouTube connection object: %error" +msgstr "Authentifierungsfehler beim Empfang des YouTube-Verbindungsobjekts: %error" + +#: includes/apiclient.inc:161 +msgid "youtbe_video" +msgstr "" + +#: includes/apiclient.inc:161 +msgid "Couldn't find the Zend client libraries." +msgstr "Die Zend-Client-Bibliotheken konnten nicht gefunden werden." + +#: includes/common.inc:156;199;285;337;385 +msgid "Your browser is not able to display this multimedia content." +msgstr "Dieser Browser kann diesen Multimedia-Inhalt nicht anzeigen." + +#: includes/common.inc:0 +msgid "1 year" +msgid_plural "@count years" +msgstr[0] "1 Jahr" +msgstr[1] "@count Jahre" + +#: includes/common.inc:0 +msgid "1 week" +msgid_plural "@count weeks" +msgstr[0] "1 Woche" +msgstr[1] "@count Wochen" + +#: includes/common.inc:0 +msgid "1 day" +msgid_plural "@count days" +msgstr[0] "1 Tag" +msgstr[1] "@count Tage" + +#: includes/common.inc:0 +msgid "1 hour" +msgid_plural "@count hours" +msgstr[0] "1 Stunde" +msgstr[1] "@count Stunden" + +#: includes/common.inc:0 +msgid "1 min" +msgid_plural "@count min" +msgstr[0] "1 Minute" +msgstr[1] "@count Minuten" + +#: includes/common.inc:0 +msgid "1 sec" +msgid_plural "@count sec" +msgstr[0] "1 Sekunde" +msgstr[1] "@count Sekunden" + +#: plugins/ffmpeg.inc:177 +#: plugins/ffmpeg_wrapper.inc:179 +msgid "error generating thumbnail for video: generated file %file does not exist.
Command Executed:
%cmd
Command Output:
%out" +msgstr "Fehler beim Erzeugen der Video-Miniaturansicht: erzeugte Datei %file existiert nicht.
Ausgeführter Befehl:
%cmd
Befehlsausgabe:
%out" + +#: types/uploadfield/uploadfield_convert.inc:52;63 +msgid "Video submission queued for processing. Please wait: our servers are preparing your video for web displaying." +msgstr "Das gespeicherte Video wurde zur Verarbeitung in die Warteschlange gestellt. Bitte warten: unsere Server bereiten das Video für die Anzeige im Web vor." + +#: types/uploadfield/uploadfield_file.inc:121 +msgid "An image thumbnail was not able to be created." +msgstr "Eine Bild-Miniaturansicht konnte nicht erstellt werden." + +#: types/uploadfield/uploadfield_formatter.inc:150 +msgid "This video is currently being processed. Please wait." +msgstr "Dieses Video wird momentan verarbeitet. Bitte warten." + +#: types/uploadfield/uploadfield_formatter.inc:158 +msgid "The video conversion process has failed. You might want to submit a simpler video format like mpeg or divx avi.
If the problem persists please contact website administrators." +msgstr "Der Video-Konvertierungsprozess ist fehlgeschlagen. Es sollte ein einfacheres Videoformat wie mpeg oder DivX avi angegeben werden.
Wenn das problem weiterhin besteht, sollten die Website-Administratoren kontaktiert werden." + +#: types/uploadfield/uploadfield_thumb.inc:25 +msgid "Transcoder not configured properly" +msgstr "Transcoder nicht korrekt konfiguriert" + +#: types/uploadfield/uploadfield_thumb.inc:40 +msgid "Video Thumbnails" +msgstr "Video-Miniaturansichten" + +#: types/uploadfield/uploadfield_widget.inc:22 +msgid "Video Player Settings" +msgstr "Video-Player-Einstellungen" + +#: types/uploadfield/uploadfield_widget.inc:30 +msgid "Default Video Resolution" +msgstr "Standardmäßige Video-Auflösung" + +#: types/uploadfield/uploadfield_widget.inc:34 +#, fuzzy +msgid "Default player resolution WIDTHXHEIGHT in px. eg : 16:9 for widescreen or 4:3 for general screen" +msgstr "Standardmäßige Video-Auflösung Breite:Höhe in Pixeln, z.B. 16:9 für Breitformat oder 4:3 für gewöhnliches Format." + +#: types/uploadfield/uploadfield_widget.inc:39 +msgid "Default Video Player Width" +msgstr "Standardmäßige Breite des Video-Players" + +#: types/uploadfield/uploadfield_widget.inc:43 +msgid "Default player WIDTHXHEIGHT in px. eg : 640 for 640X480 player size if resolution it 4:3" +msgstr "Standardmäßge BreiteXHöhe des Players in Pixeln, z.B. 640 für die Player-Größe 640x480, wenn die Auflösung 4:3 ist." + +#: types/uploadfield/uploadfield_widget.inc:50 +msgid "Video Thumbnail Settings" +msgstr "Einstellungen für Video-Miniaturansichten" + +#: types/uploadfield/uploadfield_widget.inc:69 +msgid "Upload video thumbnail" +msgstr "Video-Miniaturansicht hochladen" + +#: types/uploadfield/uploadfield_widget.inc:69 +msgid "Replace video thumbnail with" +msgstr "Video-Miniaturansicht ersetzen durch" + +#: types/uploadfield/uploadfield_widget.inc:70 +msgid "Choose a image that will be used as default video thumbnail." +msgstr "Auswahl eines Bildes, das als standardmäßige Video-Miniaturansict verwendet wird" + +#: types/uploadfield/uploadfield_widget.inc:83 +msgid "Video Advanced Settings" +msgstr "Erweiterte Video-Einstellungen" + +#: types/uploadfield/uploadfield_widget.inc:90 +msgid "Enable auto conversion video" +msgstr "Automatische Video-Konvertierung aktivieren" + +#: types/uploadfield/uploadfield_widget.inc:91 +msgid "Use ffmpeg(Default) to automaticcally convert videos to web compatible types eg. FLV, Please make sure to configure convertor settings." +msgstr "Ffmpeg (Standard) kann verwendet werden, um Videos automatisch in Web-kompatible Formate, z.B. FLV, zu konvertieren. Es muss sichergestellt werden, dass die Konverter-Einstellungen konfiguriert sind." + +#: types/uploadfield/uploadfield_widget.inc:96 +msgid "Enable auto thumbnail video" +msgstr "Automatisches Erstellen von Video-Miniaturansichten aktivieren" + +#: types/uploadfield/uploadfield_widget.inc:97 +msgid "Use ffmpeg(Default) to automaticcally thumbnails, Please make sure to configure convertor settings." +msgstr "Ffmpeg (Standard) kann verwendet werden um automatisch Miniaturansichten zu erstellen. Es muss sichergestellt werden, dass die Konverter-Einstellungen konfiguriert sind." + +#: types/uploadfield/uploadfield_widget.inc:115 +msgid "The default image could not be uploaded. The destination %destination does not exist or is not writable by the server." +msgstr "Das Standardbild konnte nicht hochgeladen werden. Das Ziel %destination existiert nicht oder kann nicht durch den Server geschrieben werden." + +#: types/uploadfield/uploadfield_widget.inc:160 +msgid "Only web-standard videos are supported through the video widget. If needing to upload other types of files, change the widget to use a standard file upload." +msgstr "Nur standardmäßige Web-Videos werden durchc das Video-Widget unterstützt. Wenn andere Dateitypen hochgeladen werden müssen, muss das Widget in ein standardmäßigen Datei-Upload geändert werden." + +#: types/uploadfield/uploadfield_widget.inc:166 +msgid "Please specify default width in integers only (e.g. 640)." +msgstr "Bitte die standardmäßige Breite als ganze Zahl angeben (z.B. 640)" + +#: types/uploadfield/uploadfield_widget.inc:173 +msgid "Please specify a resolution in the format WIDTH:HEIGHT (e.g. 16:9)." +msgstr "Bitte die Auflösung im Format WIDTH:HEIGHT angeben( z.B. 16:9)." + +#: types/uploadfield/uploadfield_widget.inc:228 +msgid "Width for Player" +msgstr "Player-Breite" + +#: types/uploadfield/uploadfield_widget.inc:230 +msgid "Set player width(in pix) here, make sure your video have good resolution to fix on larger values." +msgstr "Player-Breite (in Pixeln) hier einstellen, es sollte auch sichergestellt werden, dass bei größeren Werten die Videos eine gute Auflösung haben." + +#: types/uploadfield/uploadfield_widget.inc:246 +msgid "Bypass auto conversion" +msgstr "Automatische Konvertierung umgehen" + +#: types/uploadfield/uploadfield_widget.inc:248 +msgid "This will bypass your auto conversion of videos." +msgstr "Dies umgeht die automatische Konvertierung von Videos." + +#: types/uploadfield/uploadfield_widget.inc:264 +msgid "Override Video Thumbnail with Default" +msgstr "Video-Miniaturansicht mit Standard überschreiben" + +#: types/uploadfield/uploadfield_widget.inc:266 +msgid "If you want to use default image instead of using actual thumbnail of video then check." +msgstr "Ankreuzen, wenn das Standardbild anstelle der aktuellen Video-Miniaturansichten verwendet werden soll." + +#: types/uploadfield/uploadfield_widget.inc:278 +msgid "Save" +msgstr "Speichern" + +#: types/uploadfield/uploadfield_widget.inc:290 +msgid "Preview" +msgstr "Vorschau" + +#: types/uploadfield/uploadfield_widget.inc:293 +msgid "Delete" +msgstr "Löschen" + +#: types/uploadfield/uploadfield.module:23 +msgid "The uploadfield module has been disabled. The FileField module needs to be installed for it to work properly." +msgstr "Das Uploadfield-Modul wurde deaktiviert. Dass FileField-Modul muss installiert werden, damit es korrekt funktioniert." + +#: types/uploadfield/uploadfield.module:205 +msgid "An edit widget for video files, including video thumbnails and transcoding to flash." +msgstr "Ein Bearbeiten-Widget für Video-Dateien inklusive Video-Miniaturansichten und Transkodieren nach Flash." + +#: types/uploadfield/uploadfield.module:296 +msgid "video" +msgstr "" + +#: types/uploadfield/uploadfield.module:298 +msgid "Displays video files with player embeded." +msgstr "Zeigt Videos mit dem eingebetteten Player an." + +#: types/uploadfield/uploadfield.module:301 +msgid "video thumbnail linked to video" +msgstr "Video-Miniaturansicht, verbunden mit dem Video" + +#: types/uploadfield/uploadfield.module:303 +msgid "Displays video thumb files then the video." +msgstr "Zeigt Video-Miniaturansichten an, dann das Video" + +#: types/uploadfield/uploadfield.module:315 +msgid "@preset of video thumbnail linked to video" +msgstr "Voreinstellung @preset der Video-Miniaturansicht, verbunden mit dem Video" + +#: types/uploadfield/uploadfield.module:445 +msgid "Upload support" +msgstr "Unterstützung für das Hochladen" + +#: types/uploadfield/uploadfield.module:446 +msgid "You can upload a video file from your computer to this website." +msgstr "Von diesem Computer kann ein Video zur Website hochgeladen werden." + +#: types/uploadfield/uploadfield.info:0 +#, fuzzy +msgid "Video Upload" +msgstr "Hochladen von Videos" + +#: types/uploadfield/uploadfield.info:0 +msgid "handle video upload for video module using filefield and CCK." +msgstr "Hochladen von Videos durch das Video-Modul mittels filefield und CCK abwickeln." + diff --git a/translations/video.pot b/translations/video.pot new file mode 100644 index 0000000..112e194 --- /dev/null +++ b/translations/video.pot @@ -0,0 +1,548 @@ +# $Id$ +# +# LANGUAGE translation of Drupal (general) +# Copyright YEAR NAME +# Generated from files: +# video_render.php,v 1.1.2.7 2010/01/08 00:25:24 heshanmw +# video_scheduler.php,v 1.1.2.8 2010/02/14 02:33:33 heshanmw +# video.admin.inc,v 1.1.2.15 2010/02/14 03:28:53 heshanmw +# video.module,v 1.69.4.17.2.7 2009/12/23 18:01:58 heshanmw +# video.info,v 1.3.4.1.4.1 2009/12/13 12:59:22 heshanmw +# uploadfield.module,v 1.1.2.24 2010/02/14 10:42:49 heshanmw +# uploadfield.info,v 1.1.2.2 2009/12/23 11:16:59 heshanmw +# video.install,v 1.4.4.1.4.6 2009/12/24 12:35:03 heshanmw +# apiclient.inc,v 1.2.4.4 2009/11/13 14:39:03 heshanmw +# common.inc,v 1.2.4.10.2.11 2010/02/14 03:02:06 heshanmw +# ffmpeg.inc,v 1.1.2.14 2010/02/13 11:31:28 heshanmw +# ffmpeg_wrapper.inc,v 1.1.2.1 2010/02/13 11:31:28 heshanmw +# uploadfield_convert.inc,v 1.1.2.12 2009/12/24 16:16:22 heshanmw +# uploadfield_file.inc,v 1.1.2.5 2009/12/23 13:46:09 heshanmw +# uploadfield_formatter.inc,v 1.1.2.14 2010/02/14 10:42:49 heshanmw +# uploadfield_thumb.inc,v 1.1.2.4 2009/12/24 16:16:22 heshanmw +# uploadfield_widget.inc,v 1.1.2.33 2010/02/14 02:32:55 heshanmw +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2010-02-14 16:02+0100\n" +"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" +"Last-Translator: NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" + +#: video_render.php:56;82;91;113;127 video_scheduler.php:132;141;165;179 +msgid "video_render" +msgstr "" + +#: video_render.php:56 +msgid "Incorrect parameters to the video_render.php script." +msgstr "" + +#: video_render.php:82 video_scheduler.php:132 +msgid "video_render.php has been called with an invalid job resource. exiting." +msgstr "" + +#: video_render.php:91 video_scheduler.php:141 +msgid "converted file is an empty file." +msgstr "" + +#: video_render.php:113 video_scheduler.php:165 +msgid "successfully converted %orig to %dest" +msgstr "" + +#: video_render.php:127 video_scheduler.php:179 +msgid "error moving video %vid_file with nid = %nid to %dir the final directory. Check folder permissions.
The script was run by %uname .
The folder owner is %fowner .
The folder permissions are %perms ." +msgstr "" + +#: video_scheduler.php:80 +msgid "video_scheduler" +msgstr "" + +#: video_scheduler.php:80 +msgid "no video conversion jobs to schedule." +msgstr "" + +#: video.admin.inc:22 +msgid "General Behavior" +msgstr "" + +#: video.admin.inc:53 +msgid "Automatically start video on page load" +msgstr "" + +#: video.admin.inc:55 +msgid "Start the video when the page and video loads" +msgstr "" + +#: video.admin.inc:60 +msgid "Automatically start video buffering" +msgstr "" + +#: video.admin.inc:62 +msgid "Start buffering video when the page and video loads" +msgstr "" + +#: video.admin.inc:67 +msgid "Video Extra Players" +msgstr "" + +#: video.admin.inc:73 +msgid "Path to OGG Cortado Player" +msgstr "" + +#: video.admin.inc:75 +msgid "Copy your cortado.jar file to Drupal root and keep the setting un-changed." +msgstr "" + +#: video.admin.inc:80 +msgid "Play HQ MP4 files in Flash Player" +msgstr "" + +#: video.admin.inc:82 +msgid "Play HQ MP4 files in Flash player." +msgstr "" + +#: video.admin.inc:99 +msgid "Video Additions" +msgstr "" + +#: video.admin.inc:106 +msgid "Video transcoder" +msgstr "" + +#: video.admin.inc:109 +msgid "Video transcoder will help you to video conversion and automatic thumbnail generaion. You must install !ffmpeg_wrapper module to enable ffmpeg_wrapper support" +msgstr "" + +#: video.admin.inc:113 +msgid "Path to Video Transcoder" +msgstr "" + +#: video.admin.inc:114 +msgid "Path to executable, you can skip this if your usign ffmpeg_wrapper module support." +msgstr "" + +#: video.admin.inc:122 +msgid "Automatic video thumbnailing" +msgstr "" + +#: video.admin.inc:128 +msgid "Path to Video Thumbnails" +msgstr "" + +#: video.admin.inc:129 +msgid "Path to save video thumbanils extracted from video" +msgstr "" + +#: video.admin.inc:134 +msgid "No of thumbnails" +msgstr "" + +#: video.admin.inc:135 +msgid "No of thumbnails extracting from video" +msgstr "" + +#: video.admin.inc:140;191 +msgid "Advanced settings" +msgstr "" + +#: video.admin.inc:146 +msgid "Video thumbnailer options" +msgstr "" + +#: video.admin.inc:147 +msgid "Provide the options for the thumbnailer. Available argument values are: " +msgstr "" + +#: video.admin.inc:147 +msgid "%videofile (the video file to thumbnail)" +msgstr "" + +#: video.admin.inc:147 +msgid "%thumbfile (a newly created temporary file to overwrite with the thumbnail)" +msgstr "" + +#: video.admin.inc:147 +msgid "Only the first two are mandatory. For example, older versions of ffmpeg should use something like: !old While newer versions should use something like: !new" +msgstr "" + +#: video.admin.inc:155 +msgid "Automatic video conversion" +msgstr "" + +#: video.admin.inc:161 +msgid "Destination video Width" +msgstr "" + +#: video.admin.inc:168 +msgid "Destination video height" +msgstr "" + +#: video.admin.inc:175 +msgid "Video bitrate" +msgstr "" + +#: video.admin.inc:176 +msgid "The video bitrate in bit/s of the converted video." +msgstr "" + +#: video.admin.inc:183 +msgid "Audio bitrate" +msgstr "" + +#: video.admin.inc:184 +msgid "The audio bitrate in bit/s of the converted video." +msgstr "" + +#: video.admin.inc:197 +msgid "Video converter options" +msgstr "" + +#: video.admin.inc:198 +msgid "Provide the ffmpeg options to configure the video conversion. Available argument values are: " +msgstr "" + +#: video.admin.inc:199 +msgid "%videofile (the video file to convert)" +msgstr "" + +#: video.admin.inc:200 +msgid "%convertfile (a newly created file to store the converted file)" +msgstr "" + +#: video.admin.inc:201 +msgid "%size (video resolution of the converted file)" +msgstr "" + +#: video.admin.inc:203 +msgid "For further informations refer to the !ffmpegdoc" +msgstr "" + +#: video.admin.inc:203 +msgid "Official FFMpeg documentation." +msgstr "" + +#: video.admin.inc:220 +msgid "You do not have installed the !ffmpeg_wrapper module to enable using its plugin, please install it." +msgstr "" + +#: video.module:16 +msgid "v_perm" +msgstr "" + +#: video.module:25 video.info:0;0 types/uploadfield/uploadfield.module:201 types/uploadfield/uploadfield.info:0 +msgid "Video" +msgstr "" + +#: video.module:26 +msgid "Configure different aspects of the video module and its plugins" +msgstr "" + +#: video.install:17 +msgid "Store video transcoding queue" +msgstr "" + +#: video.install:20 +msgid "original file id" +msgstr "" + +#: video.install:27 +msgid "Process ID" +msgstr "" + +#: video.install:34 +msgid "status of the transcoding" +msgstr "" + +#: video.install:41 +msgid "Started transcodings" +msgstr "" + +#: video.install:47 +msgid "Transcoding completed" +msgstr "" + +#: video.install:53 +msgid "Informations related to the videos" +msgstr "" + +#: video.info:0 +msgid "Allows video nodes." +msgstr "" + +#: includes/apiclient.inc:44;81 +msgid "YouTube Uploads not currently available" +msgstr "" + +#: includes/apiclient.inc:62;67 +msgid "YouTube uploads currently unavailable" +msgstr "" + +#: includes/apiclient.inc:33;40;63;68;80 +msgid "video_upload" +msgstr "" + +#: includes/apiclient.inc:33 +msgid "No YouTube username set" +msgstr "" + +#: includes/apiclient.inc:40 +msgid "No YouTube password set" +msgstr "" + +#: includes/apiclient.inc:63 +msgid "Authentication error for YouTube Account" +msgstr "" + +#: includes/apiclient.inc:68 +msgid "Authentication error for YouTube Account: %error" +msgstr "" + +#: includes/apiclient.inc:80 +msgid "No developer key set" +msgstr "" + +#: includes/apiclient.inc:104;133 +msgid "youtube_video" +msgstr "" + +#: includes/apiclient.inc:104 +msgid "Authentication error while creating a YouTube connection object: %error" +msgstr "" + +#: includes/apiclient.inc:133 +msgid "Authentication error while receiving YouTube connection object: %error" +msgstr "" + +#: includes/apiclient.inc:161 +msgid "youtbe_video" +msgstr "" + +#: includes/apiclient.inc:161 +msgid "Couldn't find the Zend client libraries." +msgstr "" + +#: includes/common.inc:156;199;285;337;385 +msgid "Your browser is not able to display this multimedia content." +msgstr "" + +#: includes/common.inc:0 +msgid "1 year" +msgid_plural "@count years" +msgstr[0] "" +msgstr[1] "" + +#: includes/common.inc:0 +msgid "1 week" +msgid_plural "@count weeks" +msgstr[0] "" +msgstr[1] "" + +#: includes/common.inc:0 +msgid "1 day" +msgid_plural "@count days" +msgstr[0] "" +msgstr[1] "" + +#: includes/common.inc:0 +msgid "1 hour" +msgid_plural "@count hours" +msgstr[0] "" +msgstr[1] "" + +#: includes/common.inc:0 +msgid "1 min" +msgid_plural "@count min" +msgstr[0] "" +msgstr[1] "" + +#: includes/common.inc:0 +msgid "1 sec" +msgid_plural "@count sec" +msgstr[0] "" +msgstr[1] "" + +#: plugins/ffmpeg.inc:177 plugins/ffmpeg_wrapper.inc:179 +msgid "error generating thumbnail for video: generated file %file does not exist.
Command Executed:
%cmd
Command Output:
%out" +msgstr "" + +#: types/uploadfield/uploadfield_convert.inc:52;63 +msgid "Video submission queued for processing. Please wait: our servers are preparing your video for web displaying." +msgstr "" + +#: types/uploadfield/uploadfield_file.inc:121 +msgid "An image thumbnail was not able to be created." +msgstr "" + +#: types/uploadfield/uploadfield_formatter.inc:150 +msgid "This video is currently being processed. Please wait." +msgstr "" + +#: types/uploadfield/uploadfield_formatter.inc:158 +msgid "The video conversion process has failed. You might want to submit a simpler video format like mpeg or divx avi.
If the problem persists please contact website administrators." +msgstr "" + +#: types/uploadfield/uploadfield_thumb.inc:25 +msgid "Transcoder not configured properly" +msgstr "" + +#: types/uploadfield/uploadfield_thumb.inc:40 +msgid "Video Thumbnails" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:22 +msgid "Video Player Settings" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:30 +msgid "Default Video Resolution" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:34 +msgid "Default player resolution WIDTHXHEIGHT in px. eg : 16:9 for widescreen or 4:3 for general screen" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:39 +msgid "Default Video Player Width" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:43 +msgid "Default player WIDTHXHEIGHT in px. eg : 640 for 640X480 player size if resolution it 4:3" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:50 +msgid "Video Thumbnail Settings" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:69 +msgid "Upload video thumbnail" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:69 +msgid "Replace video thumbnail with" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:70 +msgid "Choose a image that will be used as default video thumbnail." +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:83 +msgid "Video Advanced Settings" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:90 +msgid "Enable auto conversion video" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:91 +msgid "Use ffmpeg(Default) to automaticcally convert videos to web compatible types eg. FLV, Please make sure to configure convertor settings." +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:96 +msgid "Enable auto thumbnail video" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:97 +msgid "Use ffmpeg(Default) to automaticcally thumbnails, Please make sure to configure convertor settings." +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:115 +msgid "The default image could not be uploaded. The destination %destination does not exist or is not writable by the server." +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:160 +msgid "Only web-standard videos are supported through the video widget. If needing to upload other types of files, change the widget to use a standard file upload." +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:166 +msgid "Please specify default width in integers only (e.g. 640)." +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:173 +msgid "Please specify a resolution in the format WIDTH:HEIGHT (e.g. 16:9)." +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:228 +msgid "Width for Player" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:230 +msgid "Set player width(in pix) here, make sure your video have good resolution to fix on larger values." +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:246 +msgid "Bypass auto conversion" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:248 +msgid "This will bypass your auto conversion of videos." +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:264 +msgid "Override Video Thumbnail with Default" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:266 +msgid "If you want to use default image instead of using actual thumbnail of video then check." +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:278 +msgid "Save" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:290 +msgid "Preview" +msgstr "" + +#: types/uploadfield/uploadfield_widget.inc:293 +msgid "Delete" +msgstr "" + +#: types/uploadfield/uploadfield.module:23 +msgid "The uploadfield module has been disabled. The FileField module needs to be installed for it to work properly." +msgstr "" + +#: types/uploadfield/uploadfield.module:205 +msgid "An edit widget for video files, including video thumbnails and transcoding to flash." +msgstr "" + +#: types/uploadfield/uploadfield.module:296 +msgid "video" +msgstr "" + +#: types/uploadfield/uploadfield.module:298 +msgid "Displays video files with player embeded." +msgstr "" + +#: types/uploadfield/uploadfield.module:301 +msgid "video thumbnail linked to video" +msgstr "" + +#: types/uploadfield/uploadfield.module:303 +msgid "Displays video thumb files then the video." +msgstr "" + +#: types/uploadfield/uploadfield.module:315 +msgid "@preset of video thumbnail linked to video" +msgstr "" + +#: types/uploadfield/uploadfield.module:445 +msgid "Upload support" +msgstr "" + +#: types/uploadfield/uploadfield.module:446 +msgid "You can upload a video file from your computer to this website." +msgstr "" + +#: types/uploadfield/uploadfield.info:0 +msgid "Video Upload" +msgstr "" + +#: types/uploadfield/uploadfield.info:0 +msgid "handle video upload for video module using filefield and CCK." +msgstr "" + diff --git a/types/.cvsignore b/types/.cvsignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/types/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/types/uploadfield/uploadfield.info b/types/uploadfield/uploadfield.info new file mode 100644 index 0000000..9468461 --- /dev/null +++ b/types/uploadfield/uploadfield.info @@ -0,0 +1,9 @@ +; $Id$ +name = Video Upload +description = Handle video upload for video module using filefield and CCK. +core = 6.x +version = 6.x-4.x-dev +dependencies[] = content +dependencies[] = filefield +dependencies[] = video +package = "Video" \ No newline at end of file diff --git a/types/uploadfield/uploadfield.install b/types/uploadfield/uploadfield.install new file mode 100644 index 0000000..a2e1b65 --- /dev/null +++ b/types/uploadfield/uploadfield.install @@ -0,0 +1,25 @@ + array('element' => NULL), + 'file' => 'uploadfield.theme.inc', + ); + $theme['uploadfield_widget_item'] = array( + 'arguments' => array('element' => NULL), + 'file' => 'uploadfield.theme.inc', + ); + return $theme; +} + +/** + * Implementation of CCK's hook_widget_info(). + */ +function uploadfield_widget_info() { + return array( + 'uploadfield_widget' => array( + 'label' => t('Video'), + 'field types' => array('filefield'), + 'multiple values' => CONTENT_HANDLE_CORE, + 'callbacks' => array('default value' => CONTENT_CALLBACK_CUSTOM), + 'description' => t('An edit widget for video files, including video thumbnails and transcoding to flash.'), + ), + ); +} + + +/** + * Implementation of hook_elements(). + */ +function uploadfield_elements() { + $elements = array(); + // An uploadfield is really just a FileField with extra processing. + $filefield_elements = module_invoke('filefield', 'elements'); + $elements['uploadfield_widget'] = $filefield_elements['filefield_widget']; + $elements['uploadfield_widget']['#process'][] = 'uploadfield_widget_process'; + $elements['uploadfield_widget']['#element_validate'][] = 'uploadfield_widget_validate'; + // uploadfield needs a separate value callback to save its alt and title texts. + $elements['uploadfield_widget']['#value_callback'] = 'uploadfield_widget_value'; + return $elements; +} + +/** + * Implementation of hook_file_download. + */ +function uploadfield_file_download($filepath) { + // Return headers for default images. + if (strpos($filepath, 'video_thumbs') !== FALSE) { + $full_path = file_create_path($filepath); + if ($info = getimagesize($full_path)) { + return array( + 'Content-Type: ' . $info['mime'], + 'Content-Length: ' . filesize($full_path) + ); + } + } +} + +/** + * Implementation of CCK's hook_widget_settings(). + */ +function uploadfield_widget_settings($op, $widget) { + switch ($op) { + case 'form': + return uploadfield_widget_settings_form($widget); + case 'validate': + return uploadfield_widget_settings_validate($widget); + case 'save': + return uploadfield_widget_settings_save($widget); + } +} + +/** + * Implementation of hook_widget(). + */ +function uploadfield_widget(&$form, &$form_state, $field, $items, $delta = NULL) { + $item = array('fid' => 0, 'list' => $field['list_default'], 'data' => array('description' => '', 'video_thumb' => '')); + if (isset($items[$delta])) { + $item = array_merge($item, $items[$delta]); + } + return filefield_widget($form, $form_state, $field, $items, $delta); +} + +/** + * Implementation of CCK's hook_default_value(). + */ +function uploadfield_default_value(&$form, &$form_state, $field, $delta) { + return filefield_default_value($form, $form_state, $field, $delta); +} + +/** + * Implementation of hook_form_[form_id]_alter(). + * + * Modify the add new field form to make "Video" the default formatter. + */ +function uploadfield_form_content_field_overview_form_alter(&$form, &$form_state) { + $form['#submit'][] = 'uploadfield_form_content_field_overview_submit'; +} + +/** + * Submit handler to set a new field's formatter to "video_plain". + */ +function uploadfield_form_content_field_overview_submit(&$form, &$form_state) { + if (isset($form_state['fields_added']['_add_new_field']) && isset($form['#type_name'])) { + $new_field = $form_state['fields_added']['_add_new_field']; + $node_type = $form['#type_name']; + $field = content_fields($new_field, $node_type); + if ($field['widget']['module'] == 'uploadfield') { + foreach ($field['display_settings'] as $display_type => $display_settings) { + if ($field['display_settings'][$display_type]['format'] == 'default') { + $field['display_settings'][$display_type]['format'] = 'video_plain'; + } + } + content_field_instance_update($field); + } + } +} + +/** + * filefield source support + */ +function uploadfield_filefield_sources_widgets() { + return array('uploadfield_widget'); +} \ No newline at end of file diff --git a/types/uploadfield/uploadfield.theme.inc b/types/uploadfield/uploadfield.theme.inc new file mode 100644 index 0000000..5a81efd --- /dev/null +++ b/types/uploadfield/uploadfield.theme.inc @@ -0,0 +1,18 @@ +files/videos/" directory where files will be stored. Do not include preceding or trailing slashes.'); + array_unshift($form['path_settings']['file_path']['#element_validate'], 'video_widget_settings_file_path_validate'); + + //default settings + $default = video_default_widget_settings($widget); + $form = $form + $default; + return $form; +} + +/** + * Implementation of CCK's hook_widget_settings($op = 'validate'). + */ +function uploadfield_widget_settings_validate($widget) { + // Check that only web images are specified in the callback. + if(!video_web_extensions($widget['file_extensions'])) { + form_set_error('file_extensions', t('Only web-standard videos are supported through the videoftp widget. If needing to upload other types of files, change the widget to use a standard file upload.')); + } +} + +/** + * Implementation of CCK's hook_widget_settings($op = 'save'). + */ +function uploadfield_widget_settings_save($widget) { + $filefield_settings = module_invoke('filefield', 'widget_settings', 'save', $widget); + return array_merge($filefield_settings, array('default_video_thumb', 'default_dimensions', 'default_player_dimensions', 'autoconversion', 'autothumbnail')); +} + +/** + * Element #value_callback function. + */ +function uploadfield_widget_value($element, $edit = FALSE) { + //copied from filefield_widget_value with one change to reset our data array + if (!$edit) { + $file = field_file_load($element['#default_value']['fid']); + $item = $element['#default_value']; + } + else { + $item = array_merge($element['#default_value'], $edit); + $field = content_fields($element['#field_name'], $element['#type_name']); + + // Uploads take priority over value of fid text field. + if ($fid = filefield_save_upload($element)) { + $item['fid'] = $fid; + $item['data'] = array(); //reset our data array for thumbnails and values. + } + // Check for #filefield_value_callback values. + // Because FAPI does not allow multiple #value_callback values like it does + // for #element_validate and #process, this fills the missing functionality + // to allow FileField to be extended purely through FAPI. + elseif (isset($element['#filefield_value_callback'])) { + foreach ($element['#filefield_value_callback'] as $callback) { + $callback($element, $item); + } + } + + // Load file if the FID has changed so that it can be saved by CCK. + $file = field_file_load($item['fid']); + + // If the file entry doesn't exist, don't save anything. + if (empty($file)) { + $item = array(); + } + + // Checkboxes loose their value when empty. + // If the list field is present make sure its unchecked value is saved. + if (!empty($field['list_field']) && empty($edit['list'])) { + $item['list'] = 0; + } + } + // Merge file and item data so it is available to all widgets. + $item = array_merge($item, $file); + + return $item; +} + +/** + * Element #process callback function. + */ +function uploadfield_widget_process($element, $edit, &$form_state, $form) { + $item = $element['#value']; + $field_name = $element['#field_name']; + $delta = $element['#delta']; + $field = content_fields($element['#field_name'], $element['#type_name']); + $element['#theme'] = 'uploadfield_widget_item'; + + if (isset($element['preview']) && $element['#value']['fid'] != 0) { + $element['preview']['#value'] = theme('video_widget_preview', $element['#value']); + } + + // Title is not necessary for each individual field. + if ($field['multiple'] > 0) { + unset($element['#title']); + } + + // Create our thumbnails + if($field['widget']['autothumbnail']) { + video_thumb_process($element); + } + + // Add our extra fields if in preview mode + if(!empty($item['fid'])) { + video_widget_element_settings($element); + } + + // Lets use the clicked_button #submit[0] value here instead and see how that works out for now... + if($form_state['submitted'] == 1) { + video_widget_process($element, $form_state); + } + return $element; +} \ No newline at end of file diff --git a/types/videoftp/videoftp.info b/types/videoftp/videoftp.info new file mode 100644 index 0000000..73b9318 --- /dev/null +++ b/types/videoftp/videoftp.info @@ -0,0 +1,9 @@ +; $Id$ +name = Video FTP +description = Allows you to attach videos uploaded through FTP to nodes. +core = 6.x +version = 6.x-4.x-dev +dependencies[] = content +dependencies[] = filefield +dependencies[] = video +package = "Video" \ No newline at end of file diff --git a/types/videoftp/videoftp.install b/types/videoftp/videoftp.install new file mode 100644 index 0000000..8349abc --- /dev/null +++ b/types/videoftp/videoftp.install @@ -0,0 +1,25 @@ + 'videoftp_js', + 'page arguments' => array(2, 3, 4), + //'access callback' => 'videoftp_edit_access', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + $items['videoftp/progress'] = array( + 'page callback' => 'videoftp_progress', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * Implementation of hook_theme(). + */ +function videoftp_theme() { + $theme = array(); + $theme['videoftp_widget'] = array( + 'arguments' => array('element' => NULL), + 'file' => 'videoftp.theme.inc', + ); + $theme['videoftp_widget_item'] = array( + 'arguments' => array('element' => NULL), + 'file' => 'videoftp.theme.inc', + ); + $theme['videoftp_widget_file'] = array( + 'arguments' => array('element' => NULL), + 'file' => 'videoftp.theme.inc', + ); + return $theme; +} + +/** + * Implementation of CCK's hook_widget_info(). + */ +function videoftp_widget_info() { + return array( + 'videoftp_widget' => array( + 'label' => t('Video FTP'), + 'field types' => array('filefield'), + 'multiple values' => CONTENT_HANDLE_CORE, + 'callbacks' => array('default value' => CONTENT_CALLBACK_CUSTOM), + 'description' => t('Widget allows you to select video files uploaded through FTP to be attached to the node.'), + ), + ); +} + +/** + * Implementation of hook_elements(). + */ +function videoftp_elements() { + return array( + 'videoftp_widget' => array( + '#input' => TRUE, + '#columns' => array('fid', 'list', 'data', 'filepath'), + '#process' => array('videoftp_widget_process'), + '#value_callback' => 'videoftp_widget_value', + ), + ); +} + +/** + * Implementation of CCK's hook_widget_settings(). + */ +function videoftp_widget_settings($op, $widget) { + //load up our include file for the widget + module_load_include('inc', 'videoftp', 'videoftp_widget'); + switch ($op) { + case 'form': + return videoftp_widget_settings_form($widget); + case 'validate': + return videoftp_widget_settings_validate($widget); + case 'save': + return videoftp_widget_settings_save($widget); + } +} + +/** + * Implementation of hook_widget(). + */ +function videoftp_widget(&$form, &$form_state, $field, $items, $delta = NULL) { + $item = array('fid' => 0, 'list' => $field['list_default'], 'data' => array('description' => '', 'video_thumb' => '')); + if (isset($items[$delta])) { + $item = array_merge($item, $items[$delta]); + } + $element = array( + '#title' => $field['widget']['label'], + '#type' => $field['widget']['type'], + '#default_value' => $item, + ); + return $element; +} + +/** + * Menu callback; Shared AHAH callback for ftp file attachment and deletions. + * + * This rebuilds the form element for a particular field item. As long as the + * form processing is properly encapsulated in the widget element the form + * should rebuild correctly using FAPI without the need for additional callbacks + * or processing. + */ +function videoftp_js($type_name, $field_name, $delta) { + module_load_include('inc', 'videoftp', 'videoftp_widget'); + $field = content_fields($field_name, $type_name); + + if (empty($field) || empty($_POST['form_build_id'])) { + // Invalid request. + drupal_set_message(t('An unrecoverable error occurred.'), 'error'); + print drupal_to_js(array('data' => theme('status_messages'))); + exit; + } + + // Build the new form. + $form_state = array('submitted' => FALSE); + $form_build_id = $_POST['form_build_id']; + $form = form_get_cache($form_build_id, $form_state); + + if (!$form) { + // Invalid form_build_id. + drupal_set_message(t('An unrecoverable error occurred. This form was missing from the server cache. Try reloading the page and submitting again.'), 'error'); + print drupal_to_js(array('data' => theme('status_messages'))); + exit; + } + + // Build the form. This calls the file field's #value_callback function and + // saves the uploaded file. Since this form is already marked as cached + // (the #cache property is TRUE), the cache is updated automatically and we + // don't need to call form_set_cache(). + $args = $form['#parameters']; + $form_id = array_shift($args); + $form['#post'] = $_POST; + $form = form_builder($form_id, $form, $form_state); + + // Update the cached form with the new element at the right place in the form. + if (module_exists('fieldgroup') && ($group_name = _fieldgroup_field_get_group($type_name, $field_name))) { + if (isset($form['#multigroups']) && isset($form['#multigroups'][$group_name][$field_name])) { + $form_element = $form[$group_name][$delta][$field_name]; + } + else { + $form_element = $form[$group_name][$field_name][$delta]; + } + } + else { + $form_element = $form[$field_name][$delta]; + } + + if (isset($form_element['_weight'])) { + unset($form_element['_weight']); + } + $output = drupal_render($form_element); + // AHAH is not being nice to us and doesn't know the "other" button (that is, + // either "Attach" or "Delete") yet. Which in turn causes it not to attach + // AHAH behaviours after replacing the element. So we need to tell it first. + + // Loop through the JS settings and find the settings needed for our buttons. + $javascript = drupal_add_js(NULL, NULL); + $videoftp_ahah_settings = array(); + if (isset($javascript['setting'])) { + foreach ($javascript['setting'] as $settings) { + if (isset($settings['ahah'])) { + foreach ($settings['ahah'] as $id => $ahah_settings) { + if (strpos($id, 'videoftp-attach') || strpos($id, 'videoftp-remove')) { + $videoftp_ahah_settings[$id] = $ahah_settings; + } + } + } + } + } + + // Add the AHAH settings needed for our new buttons. + if (!empty($videoftp_ahah_settings)) { + $output .= ''; + } + + $output = theme('status_messages') . $output; + + // For some reason, file uploads don't like drupal_json() with its manual + // setting of the text/javascript HTTP header. So use this one instead. + $GLOBALS['devel_shutdown'] = FALSE; + print drupal_to_js(array('status' => TRUE, 'data' => $output)); + exit; +} + +/** + * filefield source support + */ +function videoftp_filefield_sources_widgets() { + return array('videoftp_widget'); +} \ No newline at end of file diff --git a/types/videoftp/videoftp.theme.inc b/types/videoftp/videoftp.theme.inc new file mode 100644 index 0000000..42203cb --- /dev/null +++ b/types/videoftp/videoftp.theme.inc @@ -0,0 +1,57 @@ +'; + + if ($element['fid']['#value'] != 0) { + $output .= '
'; + $output .= drupal_render($element['preview']); + $output .= '
'; + } + + $output .= '
'; + $output .= drupal_render($element); + $output .= '
'; + $output .= '
'; + + return $output; +} + +/** + * Custom theme function for VideoFTP upload elements. + * + * This function allows us to put the "Attach" button immediately after the + * select field by respecting the #field_suffix property. + */ +function theme_videoftp_widget_file($element) { + $output .= '
'; + if (isset($element['#field_prefix'])) { + $output .= $element['#field_prefix']; + } + $size = $element['#size'] ? ' size="'. $element['#size'] .'"' : ''; + _form_set_class($element, array('form-select')); + $multiple = $element['#multiple']; + $output .= '\n"; + if (isset($element['#field_suffix'])) { + $output .= $element['#field_suffix']; + } + $output .= '
'; + + return theme('form_element', $element, $output); +} \ No newline at end of file diff --git a/types/videoftp/videoftp_widget.inc b/types/videoftp/videoftp_widget.inc new file mode 100644 index 0000000..d674660 --- /dev/null +++ b/types/videoftp/videoftp_widget.inc @@ -0,0 +1,313 @@ + 'textfield', + '#title' => t('Permitted upload file extensions.'), + '#default_value' => !empty($widget['file_extensions']) ? $widget['file_extensions'] : 'mp4 mpeg avi mpg wmv flv mov', + '#size' => 64, + '#description' => t('Extensions a user can attach to this field. Separate extensions with a space and do not include the leading dot. Leaving this blank will allow attachment of a file with any extension.'), + '#weight' => 1 + ); + $form['path_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Path settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#weight' => 2, + ); + $form['path_settings']['file_path'] = array( + '#type' => 'textfield', + '#size' => 64, + '#title' => t('File path'), + '#default_value' => ltrim(ltrim($widget['file_path'], "videos"), "/"), + '#description' => t('Optional subdirectory within the "files/videos/" directory where files will be moved and stored. Do not include preceding or trailing slashes.'), + '#element_validate' => array('video_widget_settings_file_path_validate', '_filefield_widget_settings_file_path_validate'), + ); + $form['path_settings']['ftp_path'] = array( + '#type' => 'textfield', + '#title' => t('FTP Filepath'), + '#default_value' => !empty($widget['ftp_path']) ? $widget['ftp_path'] : 'ftpvideos', + '#description' => t('The subdirectory within the "files/" directory where you have upload the videos for attachment. Once the video is attached it will be moved from this directory to the main files directory.'), + '#required' => TRUE, + '#weight' => 3, + ); + //default settings + $default = video_default_widget_settings($widget); + $form = $form + $default; + return $form; +} + +/** + * Implementation of CCK's hook_widget_settings($op = 'validate'). + * + * //@todo Integrate this into a universal function to share with uploadfield_widget + */ +function videoftp_widget_settings_validate($widget) { + // Check that only web images are specified in the callback. + if(!video_web_extensions($widget['file_extensions'])) { + form_set_error('file_extensions', t('Only web-standard videos are supported through the videoftp widget. If needing to upload other types of files, change the widget to use a standard file upload.')); + } + // Check for our ftp filepath and try to create it, if not it will throw an error. + $ftp_path = file_directory_path().'/'.$widget['ftp_path']; + file_check_directory($ftp_path, true, 'ftp_path'); +} + +/** + * Implementation of CCK's hook_widget_settings($op = 'save'). + */ +function videoftp_widget_settings_save($widget) { + return array('file_extensions', 'file_path', 'ftp_path', 'autothumbnail','autoconversion', 'default_dimensions', 'default_player_dimensions', 'default_video_thumb'); +} + +/** + * The #value_callback for the videoftp_widget type element. + */ +function videoftp_widget_value($element, $edit = FALSE) { + if (!$edit) { + // Creating so we load up our empty values. + $file = field_file_load($element['#default_value']['fid']); + $item = $element['#default_value']; + } + else { + // Reset our item array for our data. + $item = array_merge($element['#default_value'], $edit); + $field = content_fields($element['#field_name'], $element['#type_name']); + + // Uploads take priority over value of fid text field. + if ($fid = videoftp_save_upload($element)) { + $item['fid'] = $fid; + $item['data'] = array(); //reset our thumbnail + } + // Check for #videoftp_value_callback values. + // Because FAPI does not allow multiple #value_callback values like it does + // for #element_validate and #process, this fills the missing functionality + // to allow VideoFtp to be extended purely through FAPI. + elseif (isset($element['#videoftp_value_callback'])) { + foreach ($element['#videoftp_value_callback'] as $callback) { + $callback($element, $item); + } + } + + // Load file if the FID has changed so that it can be saved by CCK. + $file = field_file_load($item['fid']); + + // If the file entry doesn't exist, don't save anything. + if (empty($file)) { + $item = array(); + } + + // Checkboxes loose their value when empty. + // If the list field is present make sure its unchecked value is saved. + if (!empty($field['list_field']) && empty($edit['list'])) { + $item['list'] = 0; + } + } + // Merge file and item data so it is available to all widgets. + $item = array_merge($item, $file); + return $item; +} + +/** + * Process an individual element. + */ +function videoftp_widget_process($element, $edit, &$form_state, $form) { + $item = $element['#value']; + $field_name = $element['#field_name']; + $delta = $element['#delta']; + $field = content_fields($element['#field_name'], $element['#type_name']); + $element['#theme'] = 'videoftp_widget_item'; + + if (isset($element['preview']) && $element['#value']['fid'] != 0) { + $element['preview']['#value'] = theme('videoftp_widget_preview', $element['#value']); + } + + // Title is not necessary for each individual field. + if ($field['multiple'] > 0) { + unset($element['#title']); + } + + // Set up the buttons first since we need to check if they were clicked. + $element['videoftp_attach'] = array( + '#type' => 'submit', + '#value' => t('Attach'), + '#submit' => array('node_form_submit_build_node'), + '#ahah' => array( // with JavaScript + 'path' => 'videoftp/ahah/'. $element['#type_name'] .'/'. $element['#field_name'] .'/'. $element['#delta'], + 'wrapper' => $element['#id'] .'-ahah-wrapper', + 'method' => 'replace', + 'effect' => 'fade', + ), + '#field_name' => $element['#field_name'], + '#delta' => $element['#delta'], + '#type_name' => $element['#type_name'], + '#upload_validators' => $element['#upload_validators'], + '#weight' => 100, + '#post' => $element['#post'], + ); + $element['videoftp_remove'] = array( + // With default CCK edit forms, $element['#parents'] is array($element['#field_name'], $element['#delta']). + // However, if some module (for example, flexifield) places our widget deeper in the tree, we want to + // use that information in constructing the button name. + '#name' => implode('_', $element['#parents']) .'_videoftp_remove', + '#type' => 'submit', + '#value' => t('Remove'), + '#submit' => array('node_form_submit_build_node'), + '#ahah' => array( // with JavaScript + 'path' => 'videoftp/ahah/'. $element['#type_name'] .'/'. $element['#field_name'] .'/'. $element['#delta'], + 'wrapper' => $element['#id'] .'-ahah-wrapper', + 'method' => 'replace', + 'effect' => 'fade', + ), + '#field_name' => $element['#field_name'], + '#delta' => $element['#delta'], + '#weight' => 101, + '#post' => $element['#post'], + ); + + // Because the output of this field changes depending on the button clicked, + // we need to ask FAPI immediately if the remove button was clicked. + // It's not good that we call this private function, but + // $form_state['clicked_button'] is only available after this #process + // callback is finished. + if (_form_button_was_clicked($element['videoftp_remove'])) { + // Delete the file if it is currently unused. Note that field_file_delete() + // does a reference check in addition to our basic status check. + if (isset($edit['fid'])) { + $removed_file = field_file_load($edit['fid']); + if ($removed_file['status'] == 0) { + field_file_delete($removed_file); + } + } + $item = array('fid' => 0, 'list' => $field['list_default'], 'data' => array('description' => '', 'video_thumb' => '')); + } + + // Set access on the buttons and select. + $element['videoftp_attach']['#access'] = empty($item['fid']); + $element['videoftp_remove']['#access'] = !empty($item['fid']); + + $options = videoftp_options($field); + $element['ftpselect'] = array( + '#type' => 'select', + '#required' => isset($element['#required']) ? $element['#required'] : $field['required'], + '#options' => $options, + '#access' => empty($item['fid']), + ); + + // Set the FID. + $element['fid'] = array( + '#type' => 'hidden', + '#value' => $item['fid'], + ); + + if ($item['fid'] != 0) { + $element['preview'] = array( + '#type' => 'markup', + '#value' => theme('video_widget_preview', $item), + ); + } + + // placeholder.. will be serialized into the data column. this is a place for widgets + // to put additional data. + $element['data'] = array( + '#tree' => 'true', + '#access' => !empty($item['fid']), + ); + + // Create our thumbnails + if($field['widget']['autothumbnail']) { + video_thumb_process($element); + } + + // Add our extra fields if in preview mode + if(!empty($item['fid'])) { + video_widget_element_settings($element); + } + + // Set #element_validate in a way that it will not wipe out other + // validation functions already set by other modules. + if (empty($element['#element_validate'])) { + $element['#element_validate'] = array(); + } + array_unshift($element['#element_validate'], 'videoftp_widget_validate'); + + // Make sure field info will be available to the validator which + // does not get the values in $form. + $form_state['#field_info'][$field['field_name']] = $field; + + $element['#attributes']['id'] = $element['#id'] .'-ahah-wrapper'; + $element['#prefix'] = '
'; + $element['#suffix'] = '
'; + + // Lets use the clicked_button #submit[0] value here instead and see how that works out for now... + if($form_state['submitted'] == 1) { + video_widget_process($element, $form_state); + } + return $element; +} + +function videoftp_save_upload($element) { + global $user; + $upload_name = $element['#field_name'] .'_'. $element['#delta']; + $delta = $element['#delta']; + $field = content_fields($element['#field_name'], $element['#type_name']); + $video = $element['#post'][$field['field_name']][$delta]['ftpselect']; + $ftp_path = file_directory_path().'/'.$field['widget']['ftp_path']; + + if (empty($video) || !file_exists($ftp_path.'/'.$video)) { + return 0; + } + + $dest = filefield_widget_file_path($field); + if (!field_file_check_directory($dest, FILE_CREATE_DIRECTORY)) { + watchdog('filefield', 'The upload directory %directory for the file field %field (content type %type) could not be created or is not accessible. A newly uploaded file could not be saved in this directory as a consequence, and the upload was canceled.', array('%directory' => $dest, '%field' => $element['#field_name'], '%type' => $element['#type_name'])); + form_set_error($upload_name, t('The file could not be uploaded.')); + return 0; + } + + // Begin building the file object. + $file = new stdClass(); + $file->uid = $user->uid; + $file->filename = file_munge_filename(trim(basename($video), '.'), $field['widget']['file_extensions']); + $file->filepath = $ftp_path.'/'.$video; + $file->filemime = file_get_mimetype($file->filename); + $file->filesize = filesize($file->filepath); + $file->status = FILE_STATUS_TEMPORARY; + $file->timestamp = time(); + + //lets move our file from the ftp folder to the files directory + if(file_move($file, $dest)) { + // Insert new record to the database. + drupal_write_record('files', $file); + } + _field_file_cache($file); // cache the file in order to minimize load queries + return $file->fid; +} + +function videoftp_options($field) { + $options = array(); + $options[] = t('Select Video'); + // Lets setup our ftp_path. + $ftp_path = file_directory_path().'/'.$field['widget']['ftp_path']; + // We are going to scan the directory and pull out the available video types by extension. + $extensions = explode(" ",$field['widget']['file_extensions']); + $video_files = scandir($ftp_path); + foreach ($video_files as $file) { + $ext = pathinfo($file); + if (in_array($ext['extension'], $extensions)) { + //add the file to the options array for selection + $options["$file"] = $file; + } + } + return $options; +} \ No newline at end of file diff --git a/video.admin.inc b/video.admin.inc new file mode 100644 index 0000000..e97061f --- /dev/null +++ b/video.admin.inc @@ -0,0 +1,183 @@ + + */ +function video_transcoder_admin_settings() { + module_load_include('inc', 'video', '/includes/transcoder'); + $transcoder = new video_transcoder; + $form = $transcoder->admin_settings(); + return system_settings_form($form); +} + +/** + * Form API callback to validate the upload settings form. + */ +function video_transcoder_admin_settings_validate($form, &$form_state) { + // check ffmpeg_wrapper module installed or not + if ($form_state['values']['vid_convertor'] == 'video_ffmpeg_wrapper' && !module_exists('ffmpeg_wrapper')) { + form_set_error('vid_convertor', t('You need to download and install the !ffmpeg_wrapper module to enable this option.', array('!ffmpeg_wrapper' => l(t('FFMPEG Wrapper'), 'http://drupal.org/project/ffmpeg_wrapper')))); + } + + // add vallidations by trnacoder interface + $transcoder = $form_state['values']['vid_convertor']; + module_load_include('inc', 'video', '/includes/transcoder'); + $transcoder = new video_transcoder($transcoder); + $transcoder->admin_settings_validate($form, $form_state); +} + +/** + * Video transcoder admin settings + * @return + */ +function video_preset_admin_settings() { + module_load_include('inc', 'video', '/includes/preset'); + $preset = new video_preset(); + $form = $preset->admin_settings(); + return system_settings_form($form); +} + +/** + * Video general admin settings + * @return + */ +function video_general_admin_settings() { + $form = array(); + $form['video_autoplay'] = array( + '#type' => 'checkbox', + '#title' => t('Automatically start video on page load'), + '#default_value' => variable_get('video_autoplay', FALSE), + '#description' => t('Start the video when the page and video loads') + ); + $form['video_autobuffering'] = array( + '#type' => 'checkbox', + '#title' => t('Automatically start video buffering'), + '#default_value' => variable_get('video_autobuffering', TRUE), + '#description' => t('Start buffering video when the page and video loads') + ); + $form['video_bypass_conversion'] = array( + '#type' => 'checkbox', + '#title' => t('Bypass Video Conversion'), + '#default_value' => variable_get('video_bypass_conversion', FALSE), + '#description' => t('Bypass video conversion when creating videos.') + ); + $form['video_convert_on_save'] = array( + '#type' => 'checkbox', + '#title' => t('Video Convert on Node Submit'), + '#default_value' => variable_get('video_convert_on_save', FALSE), + '#description' => t('Convert videos on node submit.') + ); + $form['video_use_default_thumb'] = array( + '#type' => 'checkbox', + '#title' => t('Override Auto Thumbnails with Default'), + '#default_value' => variable_get('video_use_default_thumb', FALSE), + '#description' => t('Override auto thumbnails with default thumbnail.') + ); + return system_settings_form($form); +} + +/** + * Video player admin settings + * @return + */ +function video_players_admin_settings() { + $form = array(); + $form['extensions'] = array( + '#type' => 'fieldset', + '#title' => t('Video Extensions'), + '#description' => t('Here you can map specific players to each video extension type.'), + ); + //lets get all our supported extensions and players. + $extensions = video_video_extensions(); + $players = video_video_players(); + $flv_players = video_video_flv_players(); + + foreach ($extensions as $ext => $player) { + $form['extensions']['video_extension_' . $ext] = array( + '#type' => 'select', + '#title' => t('Extension:') . ' ' . $ext, + '#default_value' => variable_get('video_extension_' . $ext, $player), + '#options' => $players, + '#prefix' => '
', + '#suffix' => '
', + ); + + $form['extensions']['video_extension_' . $ext . '_flash_player'] = array( + '#type' => !empty($flv_players) ? 'radios' : 'markup', + '#title' => t('Flash Player for') . ' ' . $ext, + '#value' => !empty($flv_players) ? '' : t('No flash players detected.
You need to install !swf_tools or !flowplayer.', array('!swf_tools' => l(t('SWF Tools'), 'http://www.drupal.org/project/swftools'), '!flowplayer' => l(t('Flowplayer'), 'http://www.drupal.org/project/flowplayer'))), + '#options' => $flv_players, + '#default_value' => variable_get('video_extension_' . $ext . '_flash_player', ''), + '#prefix' => '
', + '#suffix' => '
', + ); + } + return system_settings_form($form); +} + +/** + * Video Metadata admin settings + * @return + */ +function video_metadata_admin_settings() { + module_load_include('inc', 'video', '/includes/metadata'); + $metadata = new video_metadata; + $form = $metadata->admin_settings(); + return system_settings_form($form); +} + +function video_metadata_admin_settings_validate($form, &$form_state) { + // add vallidations by metadata interface + $metadata = $form_state['values']['vid_metadata']; + module_load_include('inc', 'video', '/includes/metadata'); + $metadata = new video_metadata($metadata); + $metadata->admin_settings_validate($form, $form_state); +} + +/** + * Video cron admin settings + * @return + */ +function video_cron_admin_settings() { + $form = array(); + $form['video_cron'] = array( + '#type' => 'checkbox', + '#title' => t('Use Drupals built in cron.'), + '#default_value' => variable_get('video_cron', TRUE), + '#description' => t('If you would like to use Drupals built in cron hook, check this box. Please be warned that transcoding videos is very resource intensive. If you use poor mans cron, I highly discourage this option. I also suggest you setup your cron to call this function through CLI instead of WGET.'), + ); + $form['video_ffmpeg_instances'] = array( + '#type' => 'textfield', + '#title' => t('Total videos to convert during each cron process.'), + '#default_value' => variable_get('video_ffmpeg_instances', 5), + '#description' => t('How many videos do you want to process on each cron run? Either through hook_cron or the video_scheduler.php.'), + ); + return system_settings_form($form); +} + +/** + * File system admin settings + * @return + */ +function video_filesystem_admin_settings() { + module_load_include('inc', 'video', '/includes/filesystem'); + $filesystem = new video_filesystem; + $form = $filesystem->admin_settings(); + return system_settings_form($form); +} + +function video_filesystem_admin_settings_validate($form, &$form_state) { + // add vallidations by metadata interface + $filesystem = $form_state['values']['vid_filesystem']; + module_load_include('inc', 'video', '/includes/filesystem'); + $filesystem = new video_filesystem($filesystem); + $filesystem->admin_settings_validate($form, $form_state); +} \ No newline at end of file diff --git a/video.drush.inc b/video.drush.inc new file mode 100644 index 0000000..3bed8e7 --- /dev/null +++ b/video.drush.inc @@ -0,0 +1,30 @@ + 'Run video transcoder scheduler', + 'callback' => 'drush_video_scheduler', + 'drupal dependencies' => array('video'), + 'options' => array( + '--limit' => 'Change the number of video items to transcode', + ), + ); + + return $items; +} + +function drush_video_scheduler() { + $limit = (int) drush_get_option('limit', variable_get('video_ffmpeg_instances', 5)); + $GLOBALS['conf']['video_ffmpeg_instances'] = $limit; + + // include our conversion class (also contains our defines) + module_load_include('inc', 'video', 'includes/conversion'); + $video_conversion = new video_conversion; + $video_conversion->run_queue(); +} \ No newline at end of file diff --git a/video.info b/video.info new file mode 100644 index 0000000..b4a584b --- /dev/null +++ b/video.info @@ -0,0 +1,9 @@ +;$Id$ + +name = Video +description = Allows Creation of CCK Video Fields. +package = "Video" +dependencies[] = content +dependencies[] = filefield +core = 6.x +version = 6.x-4.x-dev \ No newline at end of file diff --git a/video.install b/video.install new file mode 100644 index 0000000..153ce1b --- /dev/null +++ b/video.install @@ -0,0 +1,142 @@ + + * + * @todo + */ + +/** + * Implementation of hook_schema(). + */ +function video_schema() { + $schema['video_files'] = array( + 'description' => t('Store video transcoding queue'), + 'fields' => array( + 'vid' => array( + 'description' => t('Video id'), + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'fid' => array( + 'description' => t('Original file id'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'nid' => array( + 'description' => t('Node id'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'status' => array( + 'description' => t('Status of the transcoding'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'dimensions' => array( + 'type' => 'varchar', + 'length' => '255', + 'default' => '', + 'description' => t('The dimensions of the video.'), + ), + 'started' => array( + 'description' => t('Started transcodings'), + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'completed' => array( + 'description' => t('Transcoding completed'), + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'data' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + 'description' => 'A serialized array of converted files. Use of this field is discouraged and it will likely disappear in a future version of Drupal.', + ), + ), + 'indexes' => array( + 'status' => array('status'), + 'file' => array('fid'), + ), + 'primary key' => array('vid'), + ); + return $schema; +} + +/** + * Implementation of hook_install(). + */ +function video_install() { + drupal_install_schema('video'); +} + +/** + * Implementation of hook_uninstall(). + */ +function video_uninstall() { + drupal_uninstall_schema('video'); +// Delete all variables which begin with the namespaced "video_*". + $video_vars = array(); + $query = "SELECT name FROM {variable} WHERE name LIKE '%video_%'"; + $video_vars = db_query($query); + while ($result = db_fetch_array($video_vars)) { + if (strpos($result['name'], 'video') === 0) { + variable_del($result['name']); + } + } +} + +/** + * Update 6405 + * dropping video_rendering table and creating video_files + * @return + */ +function video_update_6405() { + $ret = array(); + $ret[] = update_sql("DROP TABLE IF EXISTS {video_rendering}"); + $ret[] = update_sql("DROP TABLE IF EXISTS {video_files}"); + $table = video_schema(); + db_create_table($ret, 'video_files', $table['video_files']); + return $ret; +} + +/** + * Update 6406 + * @return + */ +function video_update_6406() { + drupal_set_message('The system has reset your thumbnail and ffmpeg command settings to their original state. If you made adjustments to these commands, you will have to reset them up.'); +//lets reset our ffmpeg system command variables. + variable_set('video_ffmpeg_thumbnailer_options', '-i !videofile -an -y -f mjpeg -ss !seek -vframes 1 !thumbfile'); + variable_set('video_ffmpeg_helper_auto_cvr_options', '-y -i !videofile -f flv -ar 22050 -ab !audiobitrate -s !size -b !videobitrate -qscale 1 !convertfile'); + return array(); +} + +/** + * Update 6407 + */ +function video_update_6407() { + $ret = array(); +// drop un wanted fields in video files + db_drop_field($ret, 'video_files', 'filesize'); + db_drop_field($ret, 'video_files', 'filename'); + db_drop_field($ret, 'video_files', 'filepath'); + db_drop_field($ret, 'video_files', 'filemime'); + db_add_column($ret, 'video_files', 'data', 'longtext', array('null' => TRUE)); + return $ret; +} \ No newline at end of file diff --git a/video.module b/video.module new file mode 100644 index 0000000..36119d2 --- /dev/null +++ b/video.module @@ -0,0 +1,979 @@ + 'Video', + 'description' => 'Configure different aspects of the video module and its plugins', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('video_general_admin_settings'), + 'file' => 'video.admin.inc', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_NORMAL_ITEM, + ); + $items['admin/settings/video/general'] = array( + 'title' => 'General', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 0, + ); + $items['admin/settings/video/players'] = array( + 'title' => 'Players', + 'description' => 'Configure your player settings for each video extension.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('video_players_admin_settings'), + 'access arguments' => array('administer site configuration'), + 'file' => 'video.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 1, + ); + $items['admin/settings/video/transcoders'] = array( + 'title' => 'Transcoders', + 'description' => 'Configure your transcoder to convert your videos or extra thumbnails.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('video_transcoder_admin_settings'), + 'access arguments' => array('administer site configuration'), + 'file' => 'video.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 2, + ); + $items['admin/settings/video/presets'] = array( + 'title' => 'Presets', + 'description' => 'Configure your transcoder presets to convert your videos.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('video_preset_admin_settings'), + 'access arguments' => array('administer site configuration'), + 'file' => 'video.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 3, + ); + $items['admin/settings/video/metadata'] = array( + 'title' => 'Metadata', + 'description' => 'Configure your metadata settings.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('video_metadata_admin_settings'), + 'access arguments' => array('administer site configuration'), + 'file' => 'video.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 4, + ); + + $items['admin/settings/video/filesystem'] = array( + 'title' => 'Filesystem', + 'description' => 'Configure your filesystem settings.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('video_filesystem_admin_settings'), + 'access arguments' => array('administer site configuration'), + 'file' => 'video.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 5, + ); + + $items['admin/settings/video/cron'] = array( + 'title' => 'Cron Settings', + 'description' => 'Configure your cron settings.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('video_cron_admin_settings'), + 'access arguments' => array('administer site configuration'), + 'file' => 'video.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 6, + ); + + + return $items; +} + +/** + * Implementation of hook_theme(). + */ +function video_theme() { + $theme = array(); + $theme['video_thumbnails'] = array( + 'arguments' => array('file' => NULL, 'alt' => '', 'title' => '', 'attributes' => NULL, 'getsize' => TRUE), + 'file' => 'video.theme.inc', + ); + $theme['video_widget_preview'] = array( + 'arguments' => array('item' => TRUE), + 'file' => 'video.theme.inc', + ); + $theme['video_image'] = array( + 'arguments' => array('file' => NULL, 'alt' => '', 'title' => '', 'attributes' => NULL, 'getsize' => TRUE, 'imagecache' => NULL), + 'file' => 'video.theme.inc', + ); + $theme['video_widget_video_thumb'] = array( + 'arguments' => array('item' => TRUE), + 'file' => 'video.theme.inc', + ); + $theme['video_formatter_video_plain'] = array( + 'arguments' => array('element' => NULL), + 'file' => 'video_formatter.inc', + ); + $theme['video_formatter_video_nodelink'] = array( + 'arguments' => array('element' => NULL, 'imagecache' => NULL), + 'file' => 'video_formatter.inc', + ); + //$theme['video_formatter_video_colorbox'] = array( + // 'arguments' => array('element' => NULL, 'imagecache' => NULL), + // 'file' => 'video_formatter.inc', + //); + $theme['video_formatter_video_media_js'] = array( + 'arguments' => array('element' => NULL), + 'file' => 'video_formatter.inc', + ); + $theme['video_encoding_failed'] = array( + 'arguments' => array(), + 'file' => 'video_formatter.inc', + ); + $theme['video_inprogress'] = array( + 'arguments' => array(), + 'file' => 'video_formatter.inc', + ); + + $path = drupal_get_path('module', 'video') . '/theme'; + //Lets setup our themes for our players + $players = video_video_players(); + foreach ($players as $tpl => $value) { + $theme[$tpl] = array( + 'arguments' => array('video' => NULL, 'node' => NULL, 'themed_output' => NULL), + //#843368 fix +// 'file' => 'video_formatter.inc', + 'template' => str_replace('_', '-', $tpl), + 'path' => $path, + ); + } + //We need to add an flv theme buffer to allow users to override in their own module to add in extra parameters before + //calling our flv template file. + $theme['video_flv'] = array( + 'arguments' => array('video' => NULL, 'node' => NULL), + 'file' => 'video_formatter.inc' + ); + + //setup our imagecache presets + if (module_exists('imagecache')) { + //we need formatters for each of our thumbnails. + //@todo create a function to check for our colorbox module and only add theme elements that could be used. + $thumb_types = array('video_nodelink'); //array('video_colorbox', 'video_nodelink'); + foreach ($thumb_types as $types) { + foreach (imagecache_presets () as $preset) { + $theme['video_formatter_' . $preset['presetname'] . '__' . $types] = array( + 'arguments' => array('element' => NULL), + 'function' => 'theme_video_formatter_imagecache', + 'file' => 'video_formatter.inc' + ); + } + } + } + return $theme; +} + +/** + * Implementation of CCK's hook_field_formatter_info(). + */ +function video_field_formatter_info() { + $formatters = array( + 'video_plain' => array( + 'label' => t('Video'), + 'field types' => array('filefield'), + 'description' => t('Displays video files with player embedded.'), + ), + 'video_nodelink' => array( + 'label' => t('Video Thumbnail linked to node'), + 'field types' => array('filefield'), + 'description' => t('Displays the video thumbnail and links to the node.'), + ), + //'video_colorbox' => array( + // 'label' => t('Video Thumbnail to Colorbox'), + // 'field types' => array('filefield'), + // 'description' => t('Displays the video thumbnail and adds colorbox support.'), + //), + 'video_media_js' => array( + 'label' => t('Video inject with jMedia'), + 'field types' => array('filefield'), + 'description' => t('Displays the video by using jmedia javascript.'), + ), + ); + //setup our imagecache presets + if (module_exists('imagecache')) { + //we need formatters for each of our thumbnails. + $thumb_types = array('video_nodelink'); //array('video_colorbox', 'video_nodelink'); + foreach ($thumb_types as $types) { + foreach (imagecache_presets () as $preset) { + $formatters[$preset['presetname'] . '__' . $types] = array( + 'label' => t('@preset @label', array('@preset' => $preset['presetname'], '@label' => $formatters[$types]['label'])), + 'field types' => array('filefield'), + ); + } + } + } + return $formatters; +} + +/* + * Implmentation of hook_cron(). + */ + +function video_cron() { + if (variable_get('video_cron', TRUE)) { + module_load_include('inc', 'video', '/includes/conversion'); + $video_conversion = new video_conversion; + $video_conversion->run_queue(); + } +} + +/** + * Implementation of hook_form_alter() + * @param string $form + * @param $form_state + * @param $form_id + */ +function video_form_alter(&$form, &$form_state, $form_id) { + if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] . '_node_form' == $form_id) { + $form['buttons']['submit']['#submit'][] = 'video_node_update_submit'; + } +} + +function video_node_update_submit($form, &$form_state) { + //lets update our video rending table to include the node id created + if (isset($form_state['nid']) && isset($form_state['values']['video_id']) && is_array($form_state['values']['video_id'])) { + foreach ($form_state['values']['video_id'] as $fid) { + // @TODO : check for enable trancoder + module_load_include('inc', 'video', '/includes/transcoder'); + $transcoder = new video_transcoder; + $video = array('nid' => $form_state['nid'], 'fid' => $fid); + $transcoder->update_job($video); + // Lets other module to know to update + video_module_invoke('update', $form, $form_state); + } + } +} + +/* + * Utility function that will add a preview of thumbnails for you to select when uploading videos. + */ + +function video_thumb_process(&$element) { + // Developed for ffmpeg support + $file = $element['#value']; + $delta = $file['fid']; + $field = content_fields($element['#field_name'], $element['#type_name']); + $gen_fail = FALSE; + + if (isset($element['preview']) && $file['fid'] != 0) { + $default_thumb = ''; + if (in_array($field['widget']['autothumbnail'], array('auto', 'auto_fallback'))) { + + module_load_include('inc', 'video', '/includes/transcoder'); + $transcoder = new video_transcoder; + if ($thumbs = $transcoder->generate_thumbnails($file)) { + $rnd_img = rand(0, variable_get('video_thumbs', 5) - 1); + $default_thumb = $thumbs[$rnd_img]->filepath; + + if (is_array($thumbs)) { + foreach ($thumbs as $fid => $img) { + // if file object contain url then use file name to identify object + $key = $img->filepath; + $thumbss[$key] = theme('video_thumbnails', $img, '', '', array('width' => '50'), FALSE); + } + } + } + + if (!empty($thumbss)) { + $element['data']['video_thumb'] = array( + '#type' => 'radios', + '#title' => t('Video Thumbnails'), + '#options' => $thumbss, + '#default_value' => !empty($file['data']['video_thumb']) ? $file['data']['video_thumb'] : $default_thumb, + '#weight' => 10, + '#attributes' => array('class' => 'video-thumbnails', 'onchange' => 'videoftp_thumbnail_change()', 'rel' => 'video_large_thumbnail-' . $delta), + ); + } else { + $gen_fail = TRUE; + } + } + + if ((!empty($gen_fail) && $field['widget']['autothumbnail'] == 'auto_fallback') || + $field['widget']['autothumbnail'] == 'manual_upload') { + + $element['data']['video_thumb_file'] = array( + '#name' => 'files[' . $element['#field_name'] . '_' . $element['#delta'] . '_thumbs]', + '#type' => 'file', + '#size' => '40', + '#title' => !empty($file['data']['video_thumb']) ? t('Replace the video thumbnail') : t('Upload a video thumbnail'), + '#description' => t('This thumbnail will be uploaded when the node is saved.'), + ); + + $element['data']['video_thumb'] = array( + '#type' => 'value', + '#value' => isset($file['data']['video_thumb']) ? $file['data']['video_thumb'] : false, + ); + } + + // Setup our large thumbnail that is on the left. + // @todo Add smaller video preview instead of thumbnail? + if (isset($file['data']['video_thumb']) && !empty($file['data']['video_thumb'])) { + $large_thumb = array('filepath' => $file['data']['video_thumb']); + } elseif (!empty($field['widget']['default_video_thumb'])) { + $large_thumb = $field['widget']['default_video_thumb']; + } else { + $large_thumb = array('filepath' => $default_thumb); + } + // @todo Integrate the thumbnails with imagecache. + $element['preview']['#suffix'] = '
' . theme('video_thumbnails', $large_thumb, '', '', array('width' => '150'), FALSE) . '
'; + } +} + +/** + * Adds a video to the video rendering table. + * + * If auto converting, it will convert your video to flv right now. We are passing the element by reference + * just in case we ever want to add more to the element during this process. + * + * @param $element + * Form element to get the video file from. + */ +function video_convert_process(&$element) { + $file = $element['#value']; + // Add default dimensions from our default_value if needed + if (!isset($file['data']['dimensions'])) { + $file['data']['dimensions'] = $element['data']['dimensions']['#value']; + } + $convert = false; + //we need to check if this fid has already been added to the database AND that there is in fact a fid + if (is_array($file) && isset($file['fid']) && !empty($file['fid']) && !isset($file['data']['bypass_autoconversion'])) { + $fid = $file['fid']; + //setup our conversion class and check for the fid existence. + module_load_include('inc', 'video', '/includes/conversion'); + $video_conversion = new video_conversion; + + // Lets verify that we haven't added this video already. Multiple validation fails will cause this to be ran more than once + if (!$video = $video_conversion->load_job($fid)) { + // Video has not been added to the queue yet so lets add it. + $video = array('fid' => $fid, 'dimensions' => $file['data']['dimensions']); + if (!($video_conversion->create_job($video))) + drupal_set_message(t('Something went wrong with your video job creation. Please check your recent log entries for further debugging.'), 'error'); + $convert = true; + //lets queue our node status to unpublished. + $element['#unpublish'] = true; + } elseif ($video->video_status != VIDEO_RENDERING_COMPLETE) { + //lets queue our node status to unpublished. + $element['#unpublish'] = true; + } + + // Our video should be in the database pending, lets see if we need to convert it now. + // Check if we are going from unselected to selected or if this is a new video and we have checked the checkbox + $convert_video_on_save = false; + $element_data_convert_on_save = ''; + $file_date_convet_on_save = ''; + $convert_on_save = variable_get('video_convert_on_save', FALSE); + if (isset($element['data']['convert_video_on_save']['#value'])) + $element_data_convert_on_save = $element['data']['convert_video_on_save']['#value']; + if (isset($file['data']['convert_video_on_save'])) + $file_date_convet_on_save = $file['data']['convert_video_on_save']; + $convert_video_on_save = $element_data_convert_on_save || $file_date_convet_on_save; + if (((!isset($element['#default_value']['data']['convert_video_on_save']) || !$element['#default_value']['data']['convert_video_on_save']) + && $convert_video_on_save) || ($convert && $convert_video_on_save) || $convert_on_save) { + $return = $video_conversion->process($fid); + if ($return === FALSE) { + drupal_set_message(t('Something went wrong with your video conversion. Please check your recent log entries for further debugging.'), 'error'); + } elseif ($return === TRUE) { + //we are always unpublished until we are converted. + unset($element['#unpublish']); + drupal_set_message(t('Successfully converted your video.')); + } + } elseif ($convert) { + drupal_set_message(t('Video submission queued for processing. Please wait: our servers are preparing your video for display.')); + } + } +} + +/** + * Implementation of hook_file_delete(). + */ +function video_file_delete($file) { + // @TODO : check for enable trancoder + // delete the transcoder job + module_load_include('inc', 'video', '/includes/transcoder'); + $transcoder = new video_transcoder; + $transcoder->delete_job($file); + + //now lets delete our video thumbnails and folder. + $video_thumb_path = variable_get('video_thumb_path', 'video_thumbs'); + $thumb_folder = file_directory_path() . '/' . $video_thumb_path . '/' . $file->fid; + // Recursively delete our folder and files + rmdirr($thumb_folder); + // Let other modules to know about the file delete + video_module_invoke('delete', $file); +} + +/** + * Compares passed extensions with normal video web extensions. + */ +function video_web_extensions($ext) { + $extensions = array_filter(explode(' ', $ext)); + $web_extensions = array( + 'mov', 'mp4', '3gp', '3g2', 'mpg', 'mpeg', // quicktime + 'divx', //divx + 'rm', // realplayer + 'flv', 'f4v', //flash player + 'swf', // swf player + 'dir', 'dcr', // dcr player + 'asf', 'wmv', 'avi', 'mpg', 'mpeg', // windows media + 'ogg', 'ogv', 'webm' // ogg/ogv theora + ); + if (count(array_diff($extensions, $web_extensions))) { + return FALSE; + } + return TRUE; +} + +/** + * Implementation of hook_views_api(). + */ +function video_views_api() { + return array( + 'api' => 2.0, + 'path' => drupal_get_path('module', 'video') . '/views', + ); +} + +/** + * Process elements loads on settings + * @param $element + */ +function video_widget_element_settings(&$element) { + $file = $element['#value']; + $delta = $element['#delta']; + $field = content_fields($element['#field_name'], $element['#type_name']); + // Check if using the default width and replace tokens. + $default_dimensions = user_access('override player dimensions'); + $description = t('Set your video dimensions. This will create your player with these dimensions.'); + //setup our default dimensions. + $dimensions = $field['widget']['default_dimensions']; + $player_dimensions = $field['widget']['default_player_dimensions']; + // Lets figure out our dimensions for our video and add astericks next to our options. + $options = video_explode("\n", variable_get("video_metadata_dimensions", video_default_dimensions())); + if ($field['widget']['autoconversion'] && isset($element['preview']) && $file['fid'] != 0 && $default_dimensions) { + $video_info = _video_dimensions_options($options, $file['filepath']); + $description = t('Set your video dimensions. This will create your player and transcode your video with these dimensions. Your video size is !size, if you choose a higher resolution, this could cause video distortion. You are shown dimensions that match your aspect ratio, if you choose dimensions that do not match your ratio, we will pad your video by adding black bars on either the top or bottom while maintaining your videos original aspect ratio.', array('!size' => $video_info['width'] . 'x' . $video_info['height'])); + //setup our default display of dimensions. + //lets go through our options looking for a matching resolution + foreach ($options as $key => $value) { + if (stristr($value, t('(Matches Resolution)')) == TRUE) { + $dimensions = $key; + break; + } + } + } + // Override our dimensions to the user selected. + if (isset($file['data']['dimensions']) && !empty($file['data']['dimensions'])) { + $dimensions = $file['data']['dimensions']; + } + + // Override our player dimensions to the user selected. + if (isset($file['data']['player_dimensions']) && !empty($file['data']['player_dimensions'])) { + $player_dimensions = $file['data']['player_dimensions']; + } + + $element['data']['dimensions'] = array( + '#type' => 'select', + '#title' => t('Dimensions for Video Transcoding'), + '#default_value' => $dimensions, + '#description' => $description, + '#options' => $options, + ); + $element['data']['player_dimensions'] = array( + '#type' => 'select', + '#title' => t('Dimensions for Video Player'), + '#default_value' => $player_dimensions, + '#description' => t('WxH of your video player.'), + '#options' => $options, + ); + // If users cannot change the default dimensions, lets change this to a value. + if (!$default_dimensions) { + $element['data']['dimensions']['#type'] = 'value'; + $element['data']['dimensions']['#value'] = $dimensions; + $element['data']['player_dimensions']['#type'] = 'value'; + $element['data']['player_dimensions']['#value'] = $player_dimensions; + } + + // only in preview mode and then create thumbnails + if ($field['widget']['autoconversion']) { + if (user_access('bypass conversion video')) { + $element['data']['bypass_autoconversion'] = array( + '#type' => 'checkbox', + '#title' => t('Bypass auto conversion'), + '#default_value' => isset($file['data']['bypass_autoconversion']) ? $file['data']['bypass_autoconversion'] : variable_get('video_bypass_conversion', FALSE), + '#description' => t('This will bypass your auto conversion of videos.'), + '#attributes' => array('class' => 'video-bypass-auto-conversion'), + ); + } + + $convert = isset($file['data']['convert_video_on_save']) ? $file['data']['convert_video_on_save'] : variable_get('video_convert_on_save', FALSE); + if (user_access('convert on submission')) { + $element['data']['convert_video_on_save'] = array( + '#type' => 'checkbox', + '#title' => t('Convert video on save'), + '#default_value' => $convert, + '#description' => t('This will convert your video to flv format when you save, instead of scheduling it for cron.'), + '#attributes' => array('class' => 'video-convert-video-on-save'), + ); + + if ($convert) { + $element['data']['convert_video_on_save']['#attributes']['checked'] = 'checked'; + } + } else { + $element['data']['convert_video_on_save'] = array( + '#type' => 'value', + '#value' => $convert, + ); + } + + $default_thumb = isset($file['data']['use_default_video_thumb']) ? $file['data']['use_default_video_thumb'] : variable_get('video_use_default_thumb', FALSE); + if (user_access('use default thumb')) { + $element['data']['use_default_video_thumb'] = array( + '#type' => 'checkbox', + '#title' => t('Use the default thumbnail for this video?'), + '#default_value' => $default_thumb, + '#description' => t('This will set a flag for this video to use the default video thumbnail when outputed..'), + '#attributes' => array('class' => 'video-use-default-video-thumb'), + ); + if ($default_thumb) { + $element['data']['use_default_video_thumb']['#attributes']['checked'] = 'checked'; + } + } else { + $element['data']['use_default_video_thumb'] = array( + '#type' => 'value', + '#value' => $default_thumb, + ); + } + } +} + +/** + * Video_widget_process for API handlers for any video types. + * @param $element + * @param $form_state + */ +function video_widget_process(&$element, &$form_state) { + $item = $element['#value']; + $field = content_fields($element['#field_name'], $element['#type_name']); + switch ($form_state['clicked_button']['#submit'][0]) { + case 'node_form_submit': + // Auto convert our video file + if ($field['widget']['autoconversion']) { + video_convert_process($element); + //lets set our node status to unpublished if our video is not converted. + if (isset($element['#unpublish']) && $element['#unpublish']) { + //unpublish the node + $form_state['values']['status'] = 0; + } + } + + // Save manually uploaded thumbs (if they exist) and add them to element + if (isset($_FILES['files']) && is_array($_FILES['files']['name'])) + if (array_key_exists($field['field_name'] . '_' . $element['#delta'] . '_thumbs', $_FILES['files']['name'])) { + video_upload_manual_thumb($element); + } + + // Call hook_video_submit API + video_module_invoke('insert', $element, $form_state); + // + //queue up the file id to update the node id in the video rendering / cdn tables. + $form_state['values']['video_id'][] = $item['fid']; + break; + case 'node_form_build_preview': + // Call hook_video_preview API + video_module_invoke('preview', $element, $form_state); + break; + case 'node_form_delete_submit': + //moved to hook_file_delete in video module. + break; + } +} + +/** + * Handle saving of manual thumbs + */ +function video_upload_manual_thumb(&$element) { + + $destination = file_directory_path() . '/video_thumbs/' . $element['#value']['fid']; + if (!field_file_check_directory($destination, FILE_CREATE_DIRECTORY)) { + form_set_error('video_thumb_upload', t('The thumbnail image could not be uploaded. The destination %destination does not exist or is not writable by the server.', array('%destination' => dirname($destination)))); + return; + } + $validators = array( + 'file_validate_is_image' => array(), + ); + + if (!$file = file_save_upload($element['#field_name'] . '_' . $element['#delta'] . '_thumbs', $validators, $destination)) { + // No upload to save we hope... or file_save_upload() reported an error on its own. + return; + } + + // Remove old image (if any) & clean up database. + $old_thumb = $element['data']['video_thumb']['#value']; + if (!empty($old_thumb)) { + if (file_delete($old_thumb)) { + db_query('DELETE FROM {files} WHERE filepath=%d', $old_thumb); + } + } + // Make the file permanent and store it in the form. + file_set_status($file, FILE_STATUS_PERMANENT); + $element['data']['video_thumb']['#value'] = $file->filepath; +} + +/* + * Function updates our options list to show matching aspect ratios and if we have a matching resolution. + * + * We will update the options array by reference and return the aspect ratio of the file. + */ + +function _video_dimensions_options(&$options, $video) { + $aspect_ratio = _video_aspect_ratio($video); + //loop through our options and find matching ratio's and also the exact width/height + foreach ($options as $key => $value) { + $wxh = explode('x', $value); + //lets check our width and height first + if ($aspect_ratio['width'] == $wxh[0] && $aspect_ratio['height'] == $wxh[1]) { + $options[$key] = $value . ' ' . t('(Matches Resolution)'); + } else { + //now lets check our ratio's + $ratio = number_format($wxh[0] / $wxh[1], 4); + if ($ratio == $aspect_ratio['ratio']) { + $options[$key] = $value . ' ' . t('(Matches Ratio)'); + } + } + } + return $aspect_ratio; +} + +/* + * Returns the width/height and aspect ratio of the video + * + * @todo: move this to the transcoder class instead? + */ + +function _video_aspect_ratio($video) { + //lets get our video dimensions from the file + module_load_include('inc', 'video', '/includes/transcoder'); + $transcoder = new video_transcoder; + $wxh = $transcoder->get_dimensions($video); + $width = $wxh['width']; + $height = $wxh['height']; + + if (!$width || !$height) { + //no width and height found just return. + watchdog('video_conversion', 'We could not determine the height and width of the video: ' . $video, array(), WATCHDOG_DEBUG); +// drupal_set_message(t('The system counld not determine the width and height of your video: !video. If transcoding, the system could have problems.', array('!video' => $video))); + return; + } + + //now lets get aspect ratio and compare our options in the select dropdown then add an asterick if any to each option representing a matching aspect ratio. + $ratio = number_format($width / $height, 4); + $aspect_ratio = array( + 'width' => $width, + 'height' => $height, + 'ratio' => $ratio, + ); + return $aspect_ratio; +} + +/* + * Return our list of video extensions and their associated player. + */ + +function video_video_extensions() { + $extensions = array( + 'divx' => 'video_play_divx', + 'mov' => 'video_play_quicktime', + '3gp' => 'video_play_quicktime', + '3g2' => 'video_play_quicktime', + 'mp4' => 'video_play_quicktime', + 'rm' => 'video_play_realmedia', + 'f4v' => 'video_play_flv', + 'flv' => 'video_play_flv', + 'swf' => 'video_play_flash', + 'dir' => 'video_play_dcr', + 'dcr' => 'video_play_dcr', + 'asf' => 'video_play_windowsmedia', + 'wmv' => 'video_play_windowsmedia', + 'avi' => 'video_play_windowsmedia', + 'mpg' => 'video_play_windowsmedia', + 'mpeg' => 'video_play_windowsmedia', + 'ogg' => 'video_play_theora', + 'ogv' => 'video_play_theora', + 'webm' => 'video_play_theora' + ); + return $extensions; +} + +/* + * Return our supported video players. + */ + +function video_video_players() { + $players = array( + 'video_play_html5' => t('HTML5 Player'), + 'video_play_divx' => t('Divx Player'), + 'video_play_quicktime' => t('Quicktime'), + 'video_play_realmedia' => t('Real Media Player'), + 'video_play_flv' => t('FLV Flash Players'), + 'video_play_flash' => t('SWF Flash Player'), + 'video_play_dcr' => t('Director/Shockwave'), + 'video_play_windowsmedia' => t('Windows Media Player'), + 'video_play_theora' => t('Theora Player'), + ); + return $players; +} + +/* + * Return our possible flash players. + */ + +function video_video_flv_players() { + $options = array(); + if (module_exists('swftools')) { + $options['swftools'] = t('SWF Tools'); + } + if (module_exists('flowplayer')) { + $options['flowplayer'] = t('Flowplayer'); + } + return $options; +} + +/** + * Get the object for the suitable player for the parameter resource + */ +function video_get_player($element) { + // Setup our node object to be passed along with the player. + $node = $element['#node']; + // Setup our video object + module_load_include('inc', 'video', '/includes/video_helper'); + $video_helper = new video_helper; + $video = $video_helper->video_object($element); + // Lets spit out our theme based on the extension + $defaults = video_video_extensions(); + $theme_function = variable_get('video_extension_' . $video->player, $defaults[$video->player]); + // Lets do some special handling for our flv files to accomdate multiple players. + if ($theme_function == 'video_play_flv') { + return theme('video_flv', $video, $node); + } else { + return theme($theme_function, $video, $node); + } +} + +function video_default_widget_settings($widget) { + $form = array(); + // Default video settings. + $form['plugins'] = array( + '#type' => 'fieldset', + '#title' => t('Video Advanced Settings'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#weight' => 10 + ); + $form['plugins']['default_dimensions'] = array( + '#type' => 'select', + '#title' => t('Default Video Resolution Dimensions'), + '#default_value' => !empty($widget['default_dimensions']) ? $widget['default_dimensions'] : '', + '#options' => video_explode("\n", variable_get("video_metadata_dimensions", video_default_dimensions())), + '#description' => t('Default transcoding resolution WIDTHxHEIGHT, in px, that FFMPEG will use to transcode your video files.') + ); + $form['plugins']['default_player_dimensions'] = array( + '#type' => 'select', + '#title' => t('Default Video Player Dimensions'), + '#default_value' => !empty($widget['default_player_dimensions']) ? $widget['default_player_dimensions'] : '', + '#options' => video_explode("\n", variable_get("video_metadata_dimensions", video_default_dimensions())), + '#description' => t('Default player WIDTHxHEIGHT in px. This is your actual player dimensions that your video will be playing in.') + ); + $form['plugins']['autoconversion'] = array( + '#type' => 'checkbox', + '#title' => t('Enable video conversion.'), + '#description' => t('Use ffmpeg(Default) to automatically convert videos to web compatible types eg. FLV, Please make sure to configure your transcoder settings.'), + '#default_value' => $widget['autoconversion'], + ); + + // Default thumbnail settings. + $form['default'] = array( + '#type' => 'fieldset', + '#title' => t('Video Thumbnail Settings'), + '#element_validate' => array('video_default_widget_settings_validate'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#weight' => 11 + ); + + $thumb_options = array( + 'auto' => 'Automatically generate thumbnails', + 'auto_fallback' => 'Automatically generate thumbnails, with fallback to manual upload', + 'manual_upload' => 'Manually upload a thumbnail', + 'no' => 'Don\'t create thumbnail', + ); + + $form['default']['autothumbnail'] = array( + '#type' => 'radios', + '#title' => t('Thumbnail Generation'), + '#options' => $thumb_options, + '#description' => t('To use ffmpeg(Default) to create thumbnails, Please make sure to configure your transcoder settings before using ffmpeg to create thumbnails.'), + '#default_value' => isset($widget['autothumbnail']) ? $widget['autothumbnail'] : 'no', + ); + + + // @TODO: Move this to the actual upload/attach when creating a node to allow the user to upload their own thumbnail for each video. + // Present a video image of the current default image. + if (!empty($widget['default_video_thumb'])) { + $form['default']['default_video_thumbnail'] = array( + '#type' => 'markup', + '#value' => theme('video_image', $widget['default_video_thumb'], '', '', array('width' => '150'), FALSE), + '#prefix' => '
', + '#suffix' => '
' + ); + } + $form['default']['default_video_thumb_upload'] = array( + '#type' => 'file', + '#title' => empty($widget['default_video_thumb']) ? t('Upload default video thumbnail') : t('Replace default video thumbnail with'), + '#description' => t('Choose a image that will be used as video thumbnail when you don\'t have video thumbnails for videos.'), + ); + // We set this value on 'validate' so we can get CCK to add it + // as a standard field setting. + $form['default_video_thumb'] = array( + '#type' => 'value', + '#value' => $widget['default_video_thumb'], + ); + return $form; +} + +/** + * Element specific validation for video default value. + * + */ +function video_default_widget_settings_validate($element, &$form_state) { + // Verify the destination exists + $destination = file_directory_path() . '/video_thumbs'; + if (!field_file_check_directory($destination, FILE_CREATE_DIRECTORY)) { + form_set_error('default_video_thumb', t('The default image could not be uploaded. The destination %destination does not exist or is not writable by the server.', array('%destination' => dirname($destination)))); + return; + } + + $validators = array( + 'file_validate_is_image' => array(), + ); + + // We save the upload here because we can't know the correct path until the file is saved. + if (!$file = file_save_upload('default_video_thumb_upload', $validators, $destination)) { + // No upload to save we hope... or file_save_upload() reported an error on its own. + return; + } + + // Remove old image (if any) & clean up database. + $old_default = $form_state['values']['default_video_thumb']; + if (!empty($old_default['fid'])) { + if (file_delete(file_create_path($old_default['filepath']))) { + db_query('DELETE FROM {files} WHERE fid=%d', $old_default['fid']); + } + } + + // Make the file permanent and store it in the form. + file_set_status($file, FILE_STATUS_PERMANENT); + $file->timestamp = time(); + $form_state['values']['default_video_thumb'] = (array) $file; +} + +function video_widget_settings_file_path_validate($element, &$form_state) { + //lets prepend our video folder to the path settings. first truncate videos/ off the end if it exists. + // #848804 + if (!module_exists('filefield_paths')) + $form_state['values']['file_path'] = 'videos/' . $form_state['values']['file_path']; +} + +/* + * #options helper function to set our key=value for the form api. + */ + +function video_explode($delimeter, $dimensions) { + $options = array(); + $values = explode($delimeter, $dimensions); + foreach ($values as $value) { + //lets check we have a value and its in the right format + if (!empty($value) && video_format_right($value)) { + $options[trim($value)] = trim($value); + } + } + return $options; +} + +function video_format_right($value) { + $format = explode("x", $value); + if (!isset($format[0]) || !is_numeric(trim($format[0]))) + return false; + if (!isset($format[1]) || !is_numeric(trim($format[1]))) + return false; + return true; +} + +/* + * Default video dimensions. + */ + +function video_default_dimensions() { + return "176x144\n352x288\n704x576\n1408x1152\n128x96\n160x120\n320x240\n640x480\n800x600\n1024x768\n1600x1200\n2048x1024\n1280x1024\n2560x2048\n5120x4096\n852x480\n1366x768\n1600x1024\n1920x1200\n2560x1600\n3200x2048\n3840x2400\n6400x4096\n7680x4800\n320x200\n640x350\n852x480\n1280x720\n1920x1080"; +} + +/* + * Utility function to remove all files and directories recursively. + */ + +function rmdirr($dir) { + if ($objs = glob($dir . "/*")) { + foreach ($objs as $obj) { + is_dir($obj) ? rmdirr($obj) : unlink($obj); + } + } + @rmdir($dir); +} diff --git a/video.theme.inc b/video.theme.inc new file mode 100644 index 0000000..dfa36e5 --- /dev/null +++ b/video.theme.inc @@ -0,0 +1,105 @@ +'; + } + + if ($getsize) { + // Use cached width and height if available. + if (!empty($file['data']['width']) && !empty($file['data']['height'])) { + $attributes['width'] = $file['data']['width']; + $attributes['height'] = $file['data']['height']; + } + // Otherwise pull the width and height from the file. + elseif (list($width, $height, $type, $image_attributes) = @getimagesize($file['filepath'])) { + $attributes['width'] = $width; + $attributes['height'] = $height; + } + } + + if (!empty($title)) { + $attributes['title'] = $title; + } + + // Alt text should be added even if it is an empty string. + $attributes['alt'] = $alt; + + // Add a timestamp to the URL to ensure it is immediately updated after editing. + $query_string = ''; + if (isset($file['timestamp'])) { + $query_character = (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE && variable_get('clean_url', '0') == '0') ? '&' : '?'; + $query_string = $query_character . $file['timestamp']; + } + + $url = file_create_url($file['filepath']) . $query_string; + $attributes['src'] = $url; + $attributes = drupal_attributes($attributes); + return ''; +} + +function theme_video_widget_preview($item) { + return theme('filefield_widget_preview', $item); +} + +function theme_video_widget_video_thumb($item = NULL) { + return '
' . theme('video_image', $item, '', '', '', FALSE) . '
'; +} + +/** + * @defgroup "Theme Callbacks" + * @{ + * @see uploadfield_theme(). + */ +function theme_video_image($file, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE, $imagecache = FALSE) { + $file = (array)$file; + //if this is imagecache skip this as the file might not be created yet + if (!$imagecache && !is_file($file['filepath'])) { + return ''; + } + + if ($getsize && $imagecache && ($image = image_get_info($file['filepath']))) { + $attributes['width'] = $image['width']; + $attributes['height'] = $image['height']; + } + elseif ($getsize) { + // Use cached width and height if available. + if (!empty($file['data']['width']) && !empty($file['data']['height'])) { + $attributes['width'] = $file['data']['width']; + $attributes['height'] = $file['data']['height']; + } + // Otherwise pull the width and height from the file. + elseif (list($width, $height, $type, $image_attributes) = @getimagesize($file['filepath'])) { + $attributes['width'] = $width; + $attributes['height'] = $height; + } + } + + if (!empty($title)) { + $attributes['title'] = $title; + } + + // Alt text should be added even if it is an empty string. + $attributes['alt'] = $alt; + + // Add a timestamp to the URL to ensure it is immediately updated after editing. + $query_string = ''; + if (isset($file['timestamp'])) { + $query_character = (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE && variable_get('clean_url', '0') == '0') ? '&' : '?'; + $query_string = $query_character . $file['timestamp']; + } + + $url = file_create_url($file['filepath']) . $query_string; + $attributes['src'] = $url; + $attributes = drupal_attributes($attributes); + return ''; +} \ No newline at end of file diff --git a/video_formatter.inc b/video_formatter.inc new file mode 100644 index 0000000..71aca50 --- /dev/null +++ b/video_formatter.inc @@ -0,0 +1,197 @@ +load_job($element['#item']['fid']); + if ($video->video_status == VIDEO_RENDERING_ACTIVE || $video->video_status == VIDEO_RENDERING_PENDING) { + return theme('video_inprogress'); + } else if ($video->video_status == VIDEO_RENDERING_FAILED) { + return theme('video_encoding_failed'); + } + } + return video_get_player($element); +} + +/* + * Renders the video thumbnail as a link to the node page. + */ + +function theme_video_formatter_video_nodelink($element, $imagecache = FALSE) { + // Inside a view $element may contain null data. In that case, just return. + if (empty($element['#item']['fid'])) + return ''; + //setup our thumbnail object + module_load_include('inc', 'video', '/includes/video_helper'); + $video_helper = new video_helper; + $thumbnail = $video_helper->thumbnail_object($element); + //get our themed image + $image = theme('video_image', $thumbnail, $thumbnail->alt, $thumbnail->title, '', TRUE, $imagecache); + $class = 'popups video video-nodelink video-' . $element['#field_name']; + return l($image, 'node/' . $element['#node']->nid, array('attributes' => array('class' => $class), 'html' => TRUE)); +} + +/* + * Renders the video thumbnail linked to the absolute filepath of the video. Colorbox is introduced to handle + * the video in an overlay. + * + * @todo Very unstable, need better methods of integration. Seems hackish right now to find out what flv player is being + * used. We are also using jmedia for all other filetypes. + */ + +function theme_video_formatter_video_colorbox($element, $imagecache = FALSE) { + global $base_path; + if (!module_exists('colorbox')) { + drupal_set_message(t('You must download and enable !colorbox for this formatter.', array('!colorbox' => l(t('Colorbox'), 'http://www.drupal.org/project/colorbox'))), 'error'); + return theme('video_formatter_video_nodelink', $element); + } + + // Inside a view $element may contain null data. In that case, just return. + if (empty($element['#item']['fid'])) + return ''; + + //load up our media plugins + drupal_add_js(drupal_get_path('module', 'video') . '/js/jquery.media.js'); + drupal_add_js(drupal_get_path('module', 'video') . '/js/jquery.metadata.js'); + drupal_add_js(drupal_get_path('module', 'video') . '/js/flowplayer-3.2.0.min.js'); + + //setup our video object + module_load_include('inc', 'video', '/includes/video_helper'); + $video_helper = new video_helper; + $video = $video_helper->video_object($element); + + $action = swftools_get_action($video->filepath); + $player = swftools_get_player($action); + $path = explode("_", $player); + $player_url = $base_path . swftools_get_player_path() . '/' . $path[0] . '/' . variable_get($player . '_file', ''); + if (stristr($player, 'flowplayer')) { + $player = 'flowplayer'; + } + //add our default settings to the Drupal.settings object + $settings = array('video' => array( + 'flvplayer' => $player_url, + 'autoplay' => $video->autoplay, + 'autobuffer' => $video->autobuffering, + 'player' => $player, + )); + drupal_add_js($settings, 'setting'); + $image = theme('video_image', $video->thumbnail, $video->thumbnail->alt, $video->thumbnail->title, '', TRUE, $imagecache); + $class = 'video-box video-' . $element['#field_name'] . '{width:\'' . $video->player_width . 'px\', height:\'' . $video->player_height . 'px\', player:\'' . $player . '\'}'; + return l($image, $video->files->{$video->player}->url, array('attributes' => array('class' => $class), 'html' => TRUE)); +} + +/* + * We are using the jMedia library to output our video files. + * + * @todo Does not work with flv files as this requires an actual flv player. Need to figure out how to best integrate the player + * into this function. + * + * We are outputing an anchor to the videofile. The jMedia functions will overtake this anchor and setup our object/embed tags. + */ + +function theme_video_formatter_video_media_js($element) { + //#913928 + $field = content_fields($element['#field_name'], $element['#type_name']); + if (!empty($field['list_field']) && !$element['#item']['list']) + return ''; + + drupal_add_js(drupal_get_path('module', 'video') . '/js/jquery.media.js'); + drupal_add_js(drupal_get_path('module', 'video') . '/js/jquery.metadata.js'); + //setup our video object + module_load_include('inc', 'video', '/includes/video_helper'); + $video_helper = new video_helper; + $video = $video_helper->video_object($element); + //lets output the link to be overtaken by jmedia + $link = l($video->filename, $video->files->{$video->player}->url, array('attributes' => array('class' => 'jmedia {width: ' . $video->player_width . ', height: ' . $video->player_height . ', autoplay: ' . $video->autoplay . '}'))); + return $link; +} + +/** + * Displays a "encoding in progress message" + */ +function theme_video_inprogress() { + return '
' . t('This video is currently being processed. Please wait.') . '
'; +} + +/** + * Display an "encoding failed" message" + */ +function theme_video_encoding_failed() { + return '
' . t('The video conversion process has failed. You might want to submit a simpler video format like mpeg or divx avi.
If the problem persists please contact website administrators.') . '
'; +} + +function theme_video_flv($video, $node) { + if ($video->flash_player == 'swftools') { + $options = array( + 'params' => array( + 'width' => $video->player_width, + 'height' => $video->player_height, + ), + 'othervars' => array( + //@todo: swftools bug, can't enable this until they fix their pathing for the images. + 'image' => $video->thumbnail->swfthumb, + ), + ); + $themed_output = swf($video->files->{$video->player}->url, $options); + } elseif ($video->flash_player == 'flowplayer') { + // kjh: use a playlist to display the thumbnail if not auto playing + if (!$video->autoplay && $video->thumbnail->url) { + $options = array( + 'playlist' => array($video->thumbnail->url, + array('url' => urlencode($video->files->{$video->player}->url), + 'autoPlay' => $video->autoplay, + 'autoBuffering' => $video->autobuffering, + ),),); + } else { + $options = array( + 'clip' => array('url' => urlencode($video->files->{$video->player}->url), + 'autoPlay' => $video->autoplay, + 'autoBuffering' => $video->autobuffering, + ),); + } + + $themed_output = theme( + 'flowplayer', + $options, + $video->formatter, + // adding 24px to height #973636 + array('style' => 'width:' . $video->player_width . 'px;height:' . ($video->player_height + 24) . 'px;') + ); + } else { + $themed_output = t('No flash player has been setup. ' . l(t('Please select a player to play Flash videos.'), 'admin/settings/video/players')); + } + return theme('video_play_flv', $video, $node, $themed_output); +} + +function theme_video_formatter_imagecache($element) { + // Inside a view $element may contain NULL data. In that case, just return. + if (empty($element['#item']['fid'])) { + return ''; + } + + // Extract the preset name from the formatter name. + list($namespace, $theme_function) = explode('__', $element['#formatter'], 2); + if ($preset = imagecache_preset_by_name($namespace)) { + $element['imagecache_preset'] = $namespace; + } + //return $theme_function; + return theme('video_formatter_' . $theme_function, $element, TRUE); +} \ No newline at end of file diff --git a/video_preset/.cvsignore b/video_preset/.cvsignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/video_preset/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/video_preset/hq_flash.inc b/video_preset/hq_flash.inc new file mode 100644 index 0000000..bf87eba --- /dev/null +++ b/video_preset/hq_flash.inc @@ -0,0 +1,77 @@ +name; + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/metadata_interface#get_help() + */ + public function get_help() { +// return t('High Video', array()); + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/metadata_interface#get_value() + */ + public function get_value() { + return $this->value; + } + + public function get_properties() { + $properties = array( + 'extension' => 'flv', + 'quality' => '', + 'speed' => '', + 'upscale' => '', + 'stretch' => '', + 'frame_rate' => '', + 'max_frame_rate' => '', + 'keyframe_interval' => '', + 'video_codec' => '', + 'video_bitrate' => '', + 'aspect_mode' => '', + 'bitrate_cap' => '', + 'buffer_size' => '', + 'h264_profile' => '', + 'h264_level' => '', + 'skip_video' => '', + 'audio_codec' => '', + 'audio_quality' => '', + 'audio_bitrate' => '', + 'audio_channels' => '', + 'audio_sample_rate' => '', + 'skip_audio' => '', + 'start_clip' => '', + 'clip_length' => '', + 'command' => array( + '!cmd_path -i !videofile -s !widthx!height -r 15 -b 250 -ar 22050 -ab 48 !convertfile' + ) + ); + return $properties; + } + +} + +?> diff --git a/video_preset/html5_mp4.inc b/video_preset/html5_mp4.inc new file mode 100644 index 0000000..d55a52c --- /dev/null +++ b/video_preset/html5_mp4.inc @@ -0,0 +1,79 @@ +name; + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/metadata_interface#get_help() + */ + public function get_help() { +// return t('HTML5 MP4 Video', array()); + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/metadata_interface#get_value() + */ + public function get_value() { + return $this->value; + } + + public function get_properties() { + $properties = array( + 'extension' => 'mp4', + 'quality' => '', + 'speed' => '', + 'upscale' => '', + 'stretch' => '', + 'frame_rate' => '', + 'max_frame_rate' => '', + 'keyframe_interval' => '', + 'video_codec' => '', + 'video_bitrate' => '', + 'aspect_mode' => '', + 'bitrate_cap' => '', + 'buffer_size' => '', + 'h264_profile' => '', + 'h264_level' => '', + 'skip_video' => '', + 'audio_codec' => '', + 'audio_quality' => '', + 'audio_bitrate' => '', + 'audio_channels' => '', + 'audio_sample_rate' => '', + 'skip_audio' => '', + 'start_clip' => '', + 'clip_length' => '', + 'command' => array( +// 'HandBrakeCLI --preset "iPhone & iPod Touch" --vb !videobitrate --width !width --two-pass --turbo --optimize --input !videofile --output !convertfile' + '!cmd_path -i !videofile -an -pass 1 -vcodec libx264 -vpre slow_firstpass -b 500k -threads 0 !convertfile', + '!cmd_path -i !videofile -acodec libfaac -ab 128k -pass 2 -vcodec libx264 -vpre slow -b 500k -threads 0 !convertfile' + ) + ); + return $properties; + } + +} + +?> \ No newline at end of file diff --git a/video_preset/html5_ogv.inc b/video_preset/html5_ogv.inc new file mode 100644 index 0000000..ca15d37 --- /dev/null +++ b/video_preset/html5_ogv.inc @@ -0,0 +1,77 @@ +name; + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/metadata_interface#get_help() + */ + public function get_help() { +// return t('HTML5 Theora Video', array()); + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/metadata_interface#get_value() + */ + public function get_value() { + return $this->value; + } + + public function get_properties() { + $properties = array( + 'extension' => 'ogv', + 'quality' => '', + 'speed' => '', + 'upscale' => '', + 'stretch' => '', + 'frame_rate' => '', + 'max_frame_rate' => '', + 'keyframe_interval' => '', + 'video_codec' => '', + 'video_bitrate' => '', + 'aspect_mode' => '', + 'bitrate_cap' => '', + 'buffer_size' => '', + 'h264_profile' => '', + 'h264_level' => '', + 'skip_video' => '', + 'audio_codec' => '', + 'audio_quality' => '', + 'audio_bitrate' => '', + 'audio_channels' => '', + 'audio_sample_rate' => '', + 'skip_audio' => '', + 'start_clip' => '', + 'clip_length' => '', + 'command' => array( + 'ffmpeg2theora --videobitrate !videobitrate --max_size !widthx!height --output !convertfile !videofile' + ) + ); + return $properties; + } + +} + +?> diff --git a/video_preset/html5_webm.inc b/video_preset/html5_webm.inc new file mode 100644 index 0000000..38f6194 --- /dev/null +++ b/video_preset/html5_webm.inc @@ -0,0 +1,78 @@ +name; + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/metadata_interface#get_help() + */ + public function get_help() { +// return t('HTML5 WebM Video', array()); + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/metadata_interface#get_value() + */ + public function get_value() { + return $this->value; + } + + public function get_properties() { + $properties = array( + 'extension' => 'webm', + 'quality' => '', + 'speed' => '', + 'upscale' => '', + 'stretch' => '', + 'frame_rate' => '', + 'max_frame_rate' => '', + 'keyframe_interval' => '', + 'video_codec' => '', + 'video_bitrate' => '', + 'aspect_mode' => '', + 'bitrate_cap' => '', + 'buffer_size' => '', + 'h264_profile' => '', + 'h264_level' => '', + 'skip_video' => '', + 'audio_codec' => '', + 'audio_quality' => '', + 'audio_bitrate' => '', + 'audio_channels' => '', + 'audio_sample_rate' => '', + 'skip_audio' => '', + 'start_clip' => '', + 'clip_length' => '', + 'command' => array( + '!cmd_path -pass 1 -passlogfile !videofile -threads 16 -keyint_min 0 -g 250 -skip_threshold 0 -qmin 1 -qmax 51 -i !videofile -vcodec libvpx -b 204800 -s !widthx!height -aspect 4:3 -an -f webm -y NUL', + '!cmd_path -pass 2 -passlogfile !videofile -threads 16 -keyint_min 0 -g 250 -skip_threshold 0 -qmin 1 -qmax 51 -i !videofile -vcodec libvpx -b 204800 -s !widthx!height -aspect 4:3 -acodec libvorbis -ac 2 -y !convertfile' + ) + ); + return $properties; + } + +} + +?> diff --git a/video_preset/iphone_mov.inc b/video_preset/iphone_mov.inc new file mode 100644 index 0000000..4c8f9ae --- /dev/null +++ b/video_preset/iphone_mov.inc @@ -0,0 +1,76 @@ +name; + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/metadata_interface#get_help() + */ + public function get_help() { +// return t('HTML5 WebM Video', array()); + } + + /** + * Interface Implementations + * @see sites/all/modules/video/includes/metadata_interface#get_value() + */ + public function get_value() { + return $this->value; + } + + public function get_properties() { + $properties = array( + 'extension' => 'mov', + 'quality' => '', + 'speed' => '', + 'upscale' => '', + 'stretch' => '', + 'frame_rate' => '', + 'max_frame_rate' => '', + 'keyframe_interval' => '', + 'video_codec' => '', + 'video_bitrate' => '', + 'aspect_mode' => '', + 'bitrate_cap' => '', + 'buffer_size' => '', + 'h264_profile' => '', + 'h264_level' => '', + 'skip_video' => '', + 'audio_codec' => '', + 'audio_quality' => '', + 'audio_bitrate' => '', + 'audio_channels' => '', + 'audio_sample_rate' => '', + 'skip_audio' => '', + 'start_clip' => '', + 'clip_length' => '', + 'command' => array( + '!cmd_path -i !videofile -f mp4 -vcodec mpeg4 -maxrate 1000k -b 700k -qmin 3 -qmax 5 -bufsize 4096 -g 300 -acodec aac -ab 192 -s 320x240 -aspect 4:3 !convertfile') + ); + return $properties; + } + +} + +?> diff --git a/video_scheduler.php b/video_scheduler.php new file mode 100644 index 0000000..4872b0b --- /dev/null +++ b/video_scheduler.php @@ -0,0 +1,137 @@ +#!/usr/bin/env php + + * + */ + +/** + * Drupal shell execution script + */ + +$script = basename(array_shift($_SERVER['argv'])); +$script_name = realpath($script); +$php_exec = realpath($_SERVER['PHP_SELF']); + +$shortopts = 'hr:s:v'; +$longopts = array('help', 'root:', 'site:', 'verbose'); + +$args = @getopt($shortopts, $longopts); + +if (isset($args['h']) || isset($args['help'])) { + echo <<run_queue(); diff --git a/views/.cvsignore b/views/.cvsignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/views/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/views/video.views.inc b/views/video.views.inc new file mode 100644 index 0000000..4d4cc36 --- /dev/null +++ b/views/video.views.inc @@ -0,0 +1,80 @@ + array( + 'path' => drupal_get_path('module', 'video') . '/views', + ), + 'handlers' => array( + // field handlers + 'video_views_handler_field_data' => array( + 'parent' => 'content_handler_field', + ), + ), + ); +} + +/** + * Implementation of hook_views_data() + */ +function video_views_data() { + $data = array(); + $data['video']['table']['group'] = t('Video'); + $widgets = array('videoftp_widget', 'uploadfield_widget'); + foreach (content_fields () as $field) { + if ($field['module'] == 'filefield' && isset($field['widget']['type']) && in_array($field['widget']['type'], $widgets)) { + $views_data = content_views_field_views_data($field); + $table_alias = content_views_tablename($field); + $db_info = content_database_info($field); + + $title = t('@label (!name) thumbnail', array('@label' => t($field['widget']['label']), '!name' => $field['field_name'])); + $types = array(); + foreach (content_types () as $type) { + if (isset($type['fields'][$field['field_name']])) { + // TODO : run check_plain here instead of on the imploded string below ? + $types[] = $type['name']; + } + } + + $additional_fields = array(); + foreach ($db_info['columns'] as $column => $attributes) { + // Select explicitly enabled field columns. + if (!empty($attributes['views'])) { + $db_columns[$column] = $attributes; + } + // Ensure all columns are retrieved. + $additional_fields[$attributes['column']] = $attributes['column']; + } + + $data[$table_alias][$field['field_name'] . '_thumbnail'] = array( + 'group' => t('Video'), + 'title' => $title, +// 'help' => t($field_types[$field['type']]['label']) .' - '. t('Appears in: @types', array('@types' => implode(', ', $types))), + // #931616 + 'help' => t('Appears in: @types', array('@types' => implode(', ', $types))), + ); + $data[$table_alias][$field['field_name'] . '_thumbnail']['field'] = array( + 'title' => $title, + 'field' => $db_info['columns']['data']['column'], + 'table' => $db_info['table'], + 'handler' => 'video_views_handler_field_data', + 'click sortable' => FALSE, + 'content_field_name' => $field['field_name'], + 'additional fields' => $additional_fields, + 'access callback' => 'content_access', + 'access arguments' => array('view', $field), + ); + } + } + return $data; +} diff --git a/views/video_views_handler_field_data.inc b/views/video_views_handler_field_data.inc new file mode 100644 index 0000000..eece913 --- /dev/null +++ b/views/video_views_handler_field_data.inc @@ -0,0 +1,60 @@ +{$this->field_alias}); + $filepath = $data['video_thumb']; + + // We're down to a single node here, so we can retrieve the actual field + // definition for the node type being considered. + $field = content_fields($this->content_field['field_name'], $values->{$this->aliases['type']}); + $options = $this->options; + $db_info = content_database_info($field); + + // Build a pseudo-node from the retrieved values. + $node = drupal_clone($values); + $node->type = $values->{$this->aliases['type']}; + $node->nid = $values->{$this->aliases['nid']}; + $node->vid = $values->{$this->aliases['vid']}; + // Some formatters need to behave differently depending on the build_mode + // (for instance: preview), so we provide one. + $node->build_mode = NODE_BUILD_NORMAL; + + $item = array(); + foreach ($db_info['columns'] as $column => $attributes) { + $item[$column] = $values->{$this->aliases[$attributes['column']]}; + } + + $item['#delta'] = $field['multiple'] ? $values->{$this->aliases['delta']} : 0; + //added for thumbnails this should work. + $file = pathinfo($filepath); + $item['filepath'] = $filepath; + $item['filename'] = $file['basename']; + $item['filemime'] = file_get_mimetype($file['basename']); + $item['filesize'] = is_readable($filepath) ? filesize($filepath) : 0; + + // Render items. + $formatter_name = $options['format']; + if ($formatter = _content_get_formatter($formatter_name, $field['type'])) { + if (content_handle('formatter', 'multiple values', $formatter) == CONTENT_HANDLE_CORE) { + // Single-value formatter. + $output = content_format($field, $item, $formatter_name, $node); + } + else { + // Multiple values formatter - we actually have only one value to display. + $output = content_format($field, array($item), $formatter_name, $node); + } + return $this->render_link($output, $values); + } + return ''; + } +} \ No newline at end of file -- cgit v1.2.3