4 * File containing class TranscoderAbstractionFactoryZencoder
8 * Class that handles Zencoder transcoding.
10 class TranscoderAbstractionFactoryZencoder
extends TranscoderAbstractionFactory implements TranscoderFactoryInterface
{
11 protected
$options = array();
13 private
$outputdestination;
15 public
function __construct() {
16 parent
::__construct();
17 $this->options
['api_key'] = variable_get('video_zencoder_api_key');
18 $this->postbackurl
= variable_get('video_zencoder_postback', url('postback/jobs', array('absolute' => TRUE
)));
19 $this->outputdestination
= variable_get('video_zencoder_output_destination');
22 public
function setInput(array $file) {
23 parent
::setInput($file);
24 $this->options
['input'] = file_create_url($this->settings
['input']['uri']);
26 if (variable_get('video_zencoder_testing_mode', FALSE
)) {
27 $this->options
['input'] = variable_get('video_zencoder_test_file_path', 'http://example.com/video.mp4');
31 public
function setOptions(array $options) {
32 foreach ($options as
$key => $value) {
33 if (empty($value) || $value === 'none') {
42 case
'video_extension':
43 $this->options
['output']['format'] = $value;
46 $this->options
['output']['size'] = $value;
49 $this->options
['output']['quality'] = intval($value);
52 $this->options
['output']['speed'] = intval($value);
55 $this->options
['output']['upscale'] = $value;
58 $this->options
['output']['one_pass'] = $value == 1;
60 case
'video_aspectmode':
61 $this->options
['output']['aspect_mode'] = $value;
64 $this->options
['output']['decoder_bitrate_cap'] = intval($value);
67 $this->options
['output']['decoder_buffer_size'] = intval($value);
70 if (strncmp('video_watermark_', $key, 16) === 0) {
73 $this->options
['output'][$key] = $value;
79 $this->options
['output']['notifications']['format'] = 'json';
80 $this->options
['output']['notifications']['url'] = $this->postbackurl
;
83 if ($this->options
['output']['thumbnails']['number'] > 0) {
84 $this->options
['output']['thumbnails'] = array(
85 'format' => $this->options
['output']['thumbnails']['format'],
86 'number' => $this->options
['output']['thumbnails']['number'],
87 'size' => variable_get('video_thumbnail_size', '320x240'),
88 'prefix' => 'thumbnail-' .
$this->settings
['input']['fid'],
92 unset($this->options
['output']['thumbnails']);
96 if (!empty($options['video_watermark_enabled']) && !empty($options['video_watermark_fid'])) {
97 $file = file_load($options['video_watermark_fid']);
98 $audioonly = !empty($options['video_watermark_onlyforaudio']);
99 $isaudio = strncmp($this->settings
['input']['filemime'], 'audio/', 6) === 0;
101 if (!empty($file) && (!$audioonly || $isaudio)) {
102 $wm = array('url' => file_create_url($file->uri
));
103 if (isset($options['video_watermark_y']) && $options['video_watermark_y'] !== '') {
104 $wm['y'] = $options['video_watermark_y'];
106 if (isset($options['video_watermark_x']) && $options['video_watermark_x'] !== '') {
107 $wm['x'] = $options['video_watermark_x'];
109 if (isset($options['video_watermark_height']) && $options['video_watermark_height'] !== '') {
110 $wm['height'] = $options['video_watermark_height'];
112 if (isset($options['video_watermark_width']) && $options['video_watermark_width'] !== '') {
113 $wm['width'] = $options['video_watermark_width'];
115 $this->options
['output']['watermarks'] = array($wm);
122 public
function setOutput($output_directory, $output_name, $overwrite_mode = FILE_EXISTS_REPLACE
) {
123 parent
::setOutput($output_directory, $output_name, $overwrite_mode);
124 $this->options
['output']['label'] = 'video-' .
$this->settings
['input']['fid'];
125 $this->options
['output']['filename'] = $this->settings
['filename'];
126 $this->options
['output']['public'] = !variable_get('video_zencoder_private', FALSE
);
129 if ($this->outputdestination
== 's3') {
130 $bucket = variable_get('amazons3_bucket');
131 // For now, silently ignore the "Use Amazon S3 module" setting when the bucket is not found
132 if ($bucket !== NULL
) {
133 $baseurl = 's3://' .
$bucket .
'/';
136 elseif ($this->outputdestination
== 'rcf') {
137 $username = variable_get('rackspace_cloud_username');
138 $key = variable_get('rackspace_cloud_api_key');
139 $container = variable_get('rackspace_cloud_container');
140 $authurl = variable_get('rackspace_cloud_auth_url');
141 // For now, silently ignore the "Use Rackspace Cloud Files module" setting when the cloudfiles module isn't setup
142 if ($username !== NULL
&& $key !== NULL
&& $container !== NULL
&& $authurl !== NULL
) {
143 $scheme = $authurl == 'https://lon.auth.api.rackspacecloud.com' ?
'cf+uk' : 'cf';
144 $baseurl = $scheme .
'://' .
rawurlencode($username) .
':' .
rawurlencode($key) .
'@' .
$container .
'/';
148 if ($baseurl != NULL
) {
149 $this->options
['output']['base_url'] = $baseurl .
file_uri_target($output_directory) .
'/';
151 if (isset($this->options
['output']['thumbnails'])) {
152 $this->options
['output']['thumbnails']['base_url'] = $baseurl .
variable_get('video_thumbnail_path', 'videos/thumbnails') .
'/' .
$this->settings
['input']['fid'] .
'/';
158 * For new videos, this function is never called, because all thumbnails are
159 * extracted and saved to the databases during the post back handler in
160 * TranscoderAbstractionFactoryZencoder::processPostback().
162 public
function extractFrames($destinationScheme, $format) {
163 // Check if the job has been completed.
164 // If the job has not been completed, don't bother checking for
166 $fid = $this->settings
['input']['fid'];
167 $job = video_jobs
::load($fid);
171 // No thumbnails available yet
172 if ($job->video_status
!= VIDEO_RENDERING_COMPLETE
) {
176 $path = variable_get('video_thumbnail_path', 'videos/thumbnails') .
'/' .
$fid;
178 // Get the file system directory.
179 $dsturibase = $destinationScheme .
'://' .
$path .
'/';
180 file_prepare_directory($dsturibase, FILE_CREATE_DIRECTORY
);
181 $dstwrapper = file_stream_wrapper_get_instance_by_scheme($destinationScheme);
183 // Find the old base url setting. If it is not present, don't check for legacy thumbnails
184 $base_url = variable_get('video_zencoder_base_url');
185 if (empty($base_url)) {
189 // Where to copy the thumbnails from.
190 $final_path = variable_get('video_zencoder_use_full_path', FALSE
) ?
drupal_realpath(file_uri_scheme($this->settings
['input']['uri']) .
'://' .
$path) : '/' .
$path;
191 $srcuribase = variable_get('video_zencoder_base_url') .
$final_path .
'/';
194 // Total thumbs to generate
195 $no_of_thumbnails = variable_get('video_thumbnail_count', 5);
196 for ($i = 0; $i < $no_of_thumbnails; $i++) {
197 $filename = file_munge_filename('thumbnail-' .
$fid .
'_' .
sprintf('%04d', $i) .
'.png', '', TRUE
);
198 $dsturi = $dsturibase .
$filename;
200 // Download file from S3, if available
201 if (!file_exists($dsturi)) {
202 $srcuri = $srcuribase .
$filename;
203 if (!file_exists($srcuri)) {
205 'Error downloading thumbnail for video %filename: %thumbpath does not exist.',
206 array('%filename' => $this->settings
['input']['filename'], '%thumbpath' => $srcuri),
211 $this->moveFile($srcuri, $dsturi);
213 // Delete the source, it is no longer needed
214 drupal_unlink($srcuri);
217 $thumb = new
stdClass();
219 $thumb->filename
= $filename;
220 $thumb->uri
= $dsturi;
221 $thumb->filemime
= $dstwrapper->getMimeType($dsturi);
225 return !empty($thumbs) ?
$thumbs : FALSE
;
228 public
function execute() {
229 libraries_load('zencoder');
230 $zencoder = new
Services_Zencoder();
233 $encoding_job = $zencoder->jobs
->create($this->options
);
235 $output = new
stdClass();
236 $output->filename
= $this->settings
['filename'];
237 $output->uri
= $this->settings
['base_url'] .
'/' .
$this->settings
['filename'];
238 $output->filesize = 0;
239 $output->timestamp
= time();
240 $output->jobid
= intval($encoding_job->id
);
241 $output->duration
= 0;
245 catch (Services_Zencoder_Exception
$e) {
246 $errors = $e->getErrors();
247 $this->errors
['execute'] = $errors;
248 watchdog('zencoder', 'Zencoder reports errors while converting %file:<br/>!errorlist', array('%file' => $this->settings
['filename'], '!errorlist' => theme('item_list', array('items' => $errors))), WATCHDOG_ERROR
);
253 public
function getName() {
257 public
function getValue() {
258 return 'TranscoderAbstractionFactoryZencoder';
261 public
function isAvailable(&$errormsg) {
264 if (!module_exists('zencoderapi')) {
265 $errormsg = t('You must install and enable the Zencoder API module to use Zencoder to transcode videos.');
268 elseif (!class_exists('Services_Zencoder')) {
269 $errormsg = t('The Zencoder API module has not been setup properly.');
276 public
function getVersion() {
280 public
function adminSettings() {
284 $zencoder_api = variable_get('video_zencoder_api_key', NULL
);
285 if (empty($zencoder_api)) {
286 $form['zencoder_user'] = array(
287 '#type' => 'fieldset',
288 '#title' => $t('Zencoder setup'),
289 '#collapsible' => FALSE
,
290 '#collapsed' => FALSE
,
291 '#description' => $t('Add your email address, password and <em>save configurations</em> to create your Zencoder account. It will help you to transcode and manage your videos using Zencode website. 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 emailed to you.', array('!link' => l($t('Zencoder.com'), 'http://zencoder.com'))),
294 ':input[name=video_convertor]' => array('value' => 'TranscoderAbstractionFactoryZencoder'),
298 $form['zencoder_user']['zencoder_username'] = array(
299 '#type' => 'textfield',
300 '#title' => $t('Your email address'),
301 '#default_value' => variable_get('site_mail', 'me@localhost'),
303 '#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.')
305 $form['zencoder_user']['agree_terms_zencoder'] = array(
306 '#type' => 'checkbox',
307 '#title' => $t('Agree Zencoder !link.', array('!link' => l($t('Terms and Conditions'), 'http://zencoder.com/terms', array('attributes' => array('target' => '_blank'))))),
308 '#default_value' => variable_get('agree_terms_zencoder', TRUE
),
312 // Zencoder API is exists
313 $form['zencoder_info'] = array(
314 '#type' => 'fieldset',
315 '#title' => t('Zencoder'),
316 '#collapsible' => FALSE
,
317 '#collapsed' => FALSE
,
320 ':input[name=video_convertor]' => array('value' => 'TranscoderAbstractionFactoryZencoder'),
324 $form['zencoder_info']['video_zencoder_api_key'] = array(
325 '#type' => 'textfield',
326 '#title' => t('Zencoder API key'),
327 '#default_value' => variable_get('video_zencoder_api_key', NULL
),
328 '#description' => t('Zencoder API Key. Click <b>Reset to default</b> button to add new account.')
330 $form['zencoder_info']['video_thumbnail_count_zc'] = array(
331 '#type' => 'textfield',
332 '#title' => t('Number of thumbnails'),
333 '#description' => t('Number of thumbnails to display from video.'),
334 '#default_value' => variable_get('video_thumbnail_count', 5),
337 $form['zencoder_info']['video_thumbnail_size'] = array(
339 '#title' => t('Dimension of thumbnails'),
340 '#default_value' => variable_get('video_thumbnail_size', '320x240'),
341 '#options' => video_utility
::getDimensions(),
343 $form['zencoder_info']['video_zencoder_postback'] = array(
344 '#type' => 'textfield',
345 '#title' => t('Postback URL for Zencoder'),
347 t('Important: Don\'t change this if you don\'t know what you\'re doing. The Postback URL is used by Zencoder to send transcoding status notifications to Drupal.') .
'<br/>' .
348 t('Default: %value', array('%value' => url('postback/jobs', array('absolute' => TRUE
)))),
349 '#default_value' => $this->postbackurl
,
351 $form['zencoder_info']['video_zencoder_postback_donotvalidate'] = array(
352 '#type' => 'checkbox',
353 '#title' => t('Do not validate the Postback URL'),
354 '#description' => t('The Postback URL is validated by retrieving the URL from the local server. In some cases this fails while it works fine for the Zencoder notification sender. Use this checkbox to disable Postback URL validation.'),
355 '#default_value' => variable_get('video_zencoder_postback_donotvalidate', FALSE
),
359 $form['zencoder_info']['testing'] = array(
360 '#type' => 'fieldset',
361 '#title' => t('Testing mode'),
362 '#collapsible' => TRUE
,
363 '#collapsed' => TRUE
,
365 $form['zencoder_info']['testing']['video_zencoder_testing_mode'] = array(
366 '#type' => 'checkbox',
367 '#title' => t('Test mode'),
368 '#default_value' => variable_get('video_zencoder_testing_mode', FALSE
),
369 '#description' => t('Enable test mode to test upload/playback locally (if you have no public IP to test)')
371 $form['zencoder_info']['testing']['video_zencoder_test_file_path'] = array(
372 '#type' => 'textfield',
373 '#title' => t('Path to test video file'),
374 '#description' => t('Add the path to a video file for Zencoder to transcode.
375 You must use this file for testing when using a local machine with no public IP
376 address from which Zencoder can download video.'),
377 '#default_value' => variable_get('video_zencoder_test_file_path', 'http://example.com/video.mp4'),
381 $form['zencoder_info']['advanced'] = array(
382 '#type' => 'fieldset',
383 '#title' => t('Advanced'),
384 '#collapsible' => TRUE
,
385 '#collapsed' => TRUE
,
388 $tempdestinations = array(
389 '' => t('Zencoder temporary storage') .
' (' .
t('default') .
')',
391 if (module_exists('amazons3') && variable_get('amazons3_bucket', FALSE
)) {
392 $tempdestinations['s3'] = t('Amazon S3 bucket %bucket', array('%bucket' => variable_get('amazons3_bucket')));
394 if (module_exists('cloud_files') && variable_get('rackspace_cloud_container', FALSE
)) {
395 $tempdestinations['rcf'] = t('Rackspace Cloud Files container %container', array('%container' => variable_get('rackspace_cloud_container')));
398 if (count($tempdestinations) > 1) {
399 $form['zencoder_info']['advanced']['video_zencoder_output_destination'] = array(
401 '#title' => t('Location for Zencoder output'),
402 '#default_value' => $this->outputdestination
=== NULL ?
'' : $this->outputdestination
,
403 '#options' => $tempdestinations,
404 '#description' => t('Normally, Zencoder uploads its transcoded files to its own Amazon S3 bucket from which the Video module will copy the file to the final destination. Use this setting to use a different location. If the selected location is identical to the final destination, this saves resource intensive copy operations during handling of the postback. The final destination is set per video field and defaults to the public files folder.'),
407 if (isset($tempdestinations['s3'])) {
408 $form['zencoder_info']['advanced']['video_zencoder_output_destination']['#description'] .
= '<br/>' .
409 t('To enable Zencoder to upload directly to your Amazon S3 bucket, read the <a href="@zencoder-s3-url">Zencoder manual</a>.', array('@zencoder-s3-url' => url('https://app.zencoder.com/docs/guides/getting-started/working-with-s3')));
413 if (module_exists('amazons3')) {
414 $form['zencoder_info']['advanced']['video_zencoder_private'] = array(
415 '#type' => 'checkbox',
416 '#title' => t('Store files on Amazon S3 privately'),
417 '#default_value' => variable_get('video_zencoder_private', FALSE
),
418 '#description' => t('Files stored privately are only accessible by visitors when <a href="@amazons3-settings">Presigned URLs</a> are enabled. These URLs expire, allow you to control access to the video files. For this setting to work, you must set %destination-setting-name to Amazon S3.', array('@amazons3-settings' => url('admin/config/media/amazons3'), '%destination-setting-name' => t('Location for Zencoder output'))),
425 public
function adminSettingsValidate($form, &$form_state) {
426 $v = $form_state['values'];
428 if (variable_get('video_zencoder_api_key', FALSE
)) {
429 // Workaround for the use of the same variable in FFmpeg
430 $form_state['values']['video_thumbnail_count'] = $form_state['values']['video_thumbnail_count_zc'];
431 unset($form_state['values']['video_thumbnail_count_zc']);
433 // Check the postback URL if validation hasn't been disabled
434 if (empty($v['video_zencoder_postback_donotvalidate'])) {
435 $testurl = $v['video_zencoder_postback'];
436 $testcode = md5(mt_rand(0, REQUEST_TIME
));
437 if (strpos($testurl, '?') === FALSE
) {
438 $testurl .
= '?test=1';
441 $testurl .
= '&test=1';
443 variable_set('video_postback_test', $testcode);
444 $result = drupal_http_request($testurl);
445 variable_del('video_postback_test');
448 if ($result->code
!= 200) {
449 $error = t('The postback URL cannot be retrieved: @error (@code).', array('@code' => $result->code
, '@error' => empty($result->error
) ?
t('unknown error') : $result->error
));
451 elseif (empty($result->data
) || trim($result->data
) != $testcode) {
452 $error = t('The postback URL is not valid: returned data contains unexpected value "@value".', array('@value' => $result->data
));
455 if ($error != NULL
) {
456 form_error($form['zencoder_info']['video_zencoder_postback'], $error);
461 // check terms and condition
462 if ($form_state['values']['agree_terms_zencoder'] == 0) {
463 form_set_error('agree_terms_zencoder', t('You must agree to the !link.', array('!link' => l(t('terms and conditions'), 'http://zencoder.com/terms'))));
465 // check for email exists
466 // Validate the e-mail address:
467 if ($error = user_validate_mail($form_state['values']['zencoder_username'])) {
468 form_set_error('zencoder_username', $error);
471 // get the API key from zencoder and save it to variable
472 if (!form_get_errors()) {
473 $mail = $form_state['values']['zencoder_username'];
474 $result = $this->createUser($mail);
475 if ($result !== TRUE
) {
476 form_set_error('zencoder_username', $result);
479 // Unset the form values because they do not need to be saved.
480 unset($form_state['values']['zencoder_username']);
481 unset($form_state['values']['agree_terms_zencoder']);
488 * Create Zencoder user account
490 protected
function createUser($mail) {
491 libraries_load('zencoder');
492 $zencoder = new
Services_Zencoder();
495 // $result is Services_Zencoder_Account
496 $result = $zencoder->accounts
->create(array(
497 'terms_of_service' => '1',
499 'affiliate_code' => 'drupal-video',
502 variable_set('video_zencoder_api_key', $result->api_key
);
503 drupal_set_message(t('Your Zencoder details are as below.<br/><b>API Key</b> : @api_key<br/> <b>Password</b> : @password<br/> You can now login to the <a href="@zencoder-url">Zencoder website</a> and track your transcoding jobs online. Make sure you <b>save user/pass combination somewhere</b> before you proceed.', array('@api_key' => $result->api_key
, '@password' => $result->password
, '@zencoder-url' => url('http://zencoder.com'))), 'status');
507 catch (Services_Zencoder_Exception
$e) {
508 if ($e->getErrors() == NULL
) {
509 return $e->getMessage();
513 foreach ($e->getErrors() as
$error) {
514 if ($error == 'Email has already been taken') {
515 drupal_set_message(t('Your account already exists on Zencoder. So <a href="@login-url">login</a> to here and enter API key below.', array('@login-url' => 'https://app.zencoder.com/session/new')));
516 variable_set('video_zencoder_api_key', t('Please enter your API key'));
526 public
function processPostback() {
527 if (strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') !== 0) {
528 echo 'This is the Zencoder notification handler. It seems to work fine.';
532 ignore_user_abort(TRUE
);
533 libraries_load('zencoder');
534 $zencoder = new
Services_Zencoder();
537 $notification = $zencoder->notifications
->parseIncoming();
538 } catch (Services_Zencoder_Exception
$e) {
539 watchdog('transcoder', 'Postback received from Zencoder could not be decoded: @errormsg', array('@errormsg' => $e->getMessage()));
544 if (!isset($notification->job
->id
)) {
545 watchdog('transcoder', 'Postback received from Zencoder is missing the job-id parameter');
550 // Check output/job state
551 $jobid = intval($notification->job
->id
);
552 $video_output = db_query('SELECT vid, original_fid, output_fid FROM {video_output} WHERE job_id = :job_id', array(':job_id' => $jobid))->fetch();
553 if (empty($video_output)) {
558 $fid = intval($video_output->original_fid
);
559 watchdog('transcoder', 'Postback received from Zencoder for fid: @fid, Zencoder job id: @jobid.', array('@fid' => $fid, '@jobid' => $jobid));
561 // Find the transcoding job.
562 $video = video_jobs
::load($fid);
564 echo 'Transcoding job not found in database';
568 // Zencoder API 2.1.0 and above use $notification->job->outputs.
569 // For now, only one output is supported.
570 $output = isset($notification->output
) ?
$notification->output
: current($notification->job
->outputs
);
572 // Find all error situations
573 if ($output->state
=== 'cancelled') {
574 video_jobs
::setFailed($video);
579 if ($output->state
=== 'failed') {
580 $errorlink = t('no specific information given');
581 if (!empty($output->error_message
)) {
582 if (!empty($output->error_link
)) {
583 $errordetail = l(t($output->error_message
), $output->error_link
);
586 $errordetail = t($output->error_message
);
590 video_jobs
::setFailed($video);
591 watchdog('transcoder', 'Zencoder reports errors in postback for fid @fid, job id @jobid: !errordetail', array('@fid' => $fid, '@jobid' => $jobid, '!errordetail' => $errordetail), WATCHDOG_ERROR
);
595 if ($notification->job
->state
!== 'finished') {
599 // Move the converted video to its final destination
600 $outputfile = file_load($video_output->output_fid
);
601 if (empty($outputfile)) {
602 echo 'Output file not found in database';
606 // Sometimes the long duration of the copy() call causes Zencoder
607 // to timeout and retry the notification postback later.
608 // So we only copy the file when it doesn't exist or has a different file size.
609 if (!file_exists($outputfile->uri
) || filesize($outputfile->uri
) != $output->file_size_in_bytes
) {
610 if (!$this->moveFile($output->url
, $outputfile->uri
)) {
611 video_jobs
::setFailed($video);
612 watchdog('transcoder', 'While processing Zencoder postback, failed to copy @source-uri to @target-uri.', array('@source-uri' => $output->url
, '@target-uri' => $outputfile->uri
), WATCHDOG_ERROR
);
617 $outputfile->filesize = $output->file_size_in_bytes
;
618 drupal_write_record('file_managed', $outputfile, 'fid');
620 // Actual processing of the response
621 $video->duration
= round($output->duration_in_ms
/ 1000);
622 video_jobs
::setCompleted($video);
624 // Clear the field cache. Normally, node_save() does this, but that function is not invoked in all cases
625 video_utility
::clearEntityCache($video->entity_type
, $video->entity_id
);
627 // If there are no thumbnails, quit now.
628 if (empty($output->thumbnails
)) {
632 // Retrieve the thumbnails from the notification structure
633 // Pre-2.1.0, each thumbnail list was an array, now it is an object
634 $thumbnails = is_array($output->thumbnails
[0]) ?
$output->thumbnails
[0]['images'] : $output->thumbnails
[0]->images
;
635 if (empty($thumbnails)) {
639 // Find the entity to which the file belongs
640 $entity = video_utility
::loadEntity($video->entity_type
, $video->entity_id
);
641 if (empty($entity)) {
642 watchdog('transcoder', 'The entity to which the transcoded video belongs can\'t be found anymore. Entity type: @entity-type, entity id: @entity-id.', array('@entity-type' => $video->entity_type
, '@entity-id' => $video->entity_id
), WATCHDOG_ERROR
);
646 // The following information was saved in video_jobs::create()
647 $fieldname = $video->data
['field_name'];
648 $field = field_info_field($fieldname);
649 $langcode = $video->data
['langcode'];
650 $delta = $video->data
['delta'];
653 if (empty($entity->{$fieldname}[$langcode][$delta])) {
654 // The field can't be found anymore. This may be a problem.
655 watchdog('transcoder', 'The field to which video @filename was uploaded doesn\'t seem to exist anymore. Entity type: @entity-type, entity id: @entity-id, field name: @fieldname, field language: @langcode, delta: @delta.', array('@filename' => $video->filename
, '@entity-type' => $video->entity_type
, '@entity-id' => $video->entity_id
, '@fieldname' => $fieldname, '@langcode' => $langcode, '@delta' => $delta), WATCHDOG_WARNING
);
658 if ($entity->{$fieldname}[$langcode][$delta]['fid'] != $video->fid
) {
659 // The field does not contain the file we uploaded.
660 watchdog('transcoder', 'The field to which video @filename was uploaded doesn\'t seem to contain this video anymore. Entity type: @entity-type, entity id: @entity-id, field name: @fieldname, field language: @langcode, delta: @delta.', array('@filename' => $video->filename
, '@entity-type' => $video->entity_type
, '@entity-id' => $video->entity_id
, '@fieldname' => $fieldname, '@langcode' => $langcode, '@delta' => $delta), WATCHDOG_WARNING
);
664 // Destination of thumbnails
665 $thumbscheme = !empty($field['settings']['uri_scheme_thumbnails']) ?
$field['settings']['uri_scheme_thumbnails'] : 'public';
666 $thumburibase = $thumbscheme .
'://' .
variable_get('video_thumbnail_path', 'videos/thumbnails') .
'/' .
$video->fid .
'/';
667 file_prepare_directory($thumburibase, FILE_CREATE_DIRECTORY
);
668 $thumbwrapper = file_stream_wrapper_get_instance_by_scheme($thumbscheme);
670 // Turn the thumbnails into managed files.
671 // Because two jobs for the same video may finish simultaneously, lock here so
672 // there are no errors when inserting the files.
673 if (!lock_acquire('video_zencoder_thumbnails:' .
$video->fid
, count($thumbnails) * 30)) {
674 if (lock_wait('video_zencoder_thumbnails:' .
$video->fid
, count($thumbnails) * 30)) {
675 watchdog('transcoder', 'Failed to acquire lock to download thumbnails for @video-filename.', array('@video-filename' => $video->filename
), WATCHDOG_ERROR
);
680 $existingthumbs = db_query('SELECT f.uri, f.fid, f.filesize FROM {file_managed} f INNER JOIN {video_thumbnails} t ON (f.fid = t.thumbnailfid) WHERE t.videofid = :fid', array(':fid' => $video->fid
))->fetchAllAssoc('uri');
683 foreach ($thumbnails as
$thumbnail) {
684 // Pre-2.1.0, each thumbnail was an array
685 $thumbnail = (object)$thumbnail;
686 $urlpath = parse_url($thumbnail->url
, PHP_URL_PATH
);
687 $ext = video_utility
::getExtension($urlpath);
689 $thumb = new
stdClass();
690 $thumb->uid
= $outputfile->uid
; // $entity may not have a uid property, so take it from the output file.
691 $thumb->status
= FILE_STATUS_PERMANENT
;
692 $thumb->filename
= 'thumbnail-' .
$video->fid .
'_' .
sprintf('%04d', $tnid++) .
'.' .
$ext;
693 $thumb->uri
= $thumburibase .
$thumb->filename
;
694 $thumb->filemime
= $thumbwrapper->getMimeType($thumb->uri
);
695 $thumb->type
= 'image'; // For the media module
696 $thumb->filesize = $thumbnail->file_size_bytes
;
697 $thumb->timestamp
= REQUEST_TIME
;
700 if (isset($existingthumbs[$thumb->uri
])) {
701 // If the thumbnail has the same size in the database compared to the notification data, don't copy
702 if (file_exists($thumb->uri
) && $existingthumbs[$thumb->uri
]->filesize == $thumb->filesize) {
705 $thumb->fid
= intval($existingthumbs[$thumb->uri
]->fid
);
706 $update = array('fid');
709 if ($shouldcopy && !$this->moveFile($thumbnail->url
, $thumb->uri
)) {
710 watchdog('transcoder', 'Could not copy @thumbsrc to @thumbdest.', array('@thumbsrc' => $thumbnail->url
, '@thumbdest' => $thumb->uri
), WATCHDOG_ERROR
);
714 drupal_write_record('file_managed', $thumb, $update);
716 // Saving to video_thumbnails and file_usage is only necessary when this is a new thumbnail
717 if (!isset($existingthumbs[$thumb->uri
])) {
718 db_insert('video_thumbnails')->fields(array('videofid' => $video->fid
, 'thumbnailfid' => $thumb->fid
))->execute();
719 file_usage_add($thumb, 'file', $video->entity_type
, $video->entity_id
);
722 $thumbs[$thumb->fid
] = $thumb;
724 lock_release('video_zencoder_thumbnails:' .
$video->fid
);
726 // Clear the field cache. Normally, node_save() does this, but that function is not invoked in all cases
727 video_utility
::clearEntityCache($video->entity_type
, $video->entity_id
);
729 // Skip setting the thumbnail if there are no thumbnails or when the current value is already valid
730 $currentthumb = isset($entity->{$fieldname}[$langcode][$delta]['thumbnail']) ?
intval($entity->{$fieldname}[$langcode][$delta]['thumbnail']) : 0;
731 if (empty($thumbs) || isset($thumbs[$currentthumb])) {
735 // Set a random thumbnail fid on the entity and save the entity
736 $entity->{$fieldname}[$langcode][$delta]['thumbnail'] = array_rand($thumbs);
738 switch ($video->entity_type
) {
743 comment_save($entity);
746 // entity_save() is supplied by the entity module
747 if (function_exists('entity_save')) {
748 entity_save($video->entity_type
, $entity);
754 private
function moveFile($srcurl, $dsturi) {
757 // If the Amazon S3 or Cloud Files module is used, we know that the file is on s3:// or rcf://.
758 // We must move the file, because the original must be deleted.
759 if ($this->outputdestination
== 's3' || $this->outputdestination
== 'rcf') {
760 $srcuri = $this->outputdestination .
':/' .
parse_url($srcurl, PHP_URL_PATH
);
761 // Check if the file is already at the right place
762 if ($srcuri === $dsturi) {
765 // Move the file if the target is also s3:// or rcf://
766 if (file_uri_scheme($dsturi) == $this->outputdestination
) {
767 return rename($srcuri, $dsturi);
770 // The src file needs to be removed after copying.
774 // Check if $srcurl is actually a $uri
776 if (strncmp('http', $srcuri, 4) === 0) {
777 $srcuri = video_utility
::urlToUri($srcurl);
778 if ($srcuri === NULL
) {
783 // Check if the file is already at the right place
784 if ($srcuri === $dsturi) {
788 $result = copy($srcuri, $dsturi);
789 if ($result && $unlinksrc) {
797 * Get enabled and supporting codecs by Zencoder.
799 public
function getCodecs() {
800 $auto = t('Default for this extension');
808 'theora' => 'Theora',
817 'vorbis' => 'Vorbis',
825 public
function getAvailableFormats($type = FALSE
) {
855 public
function getPixelFormats() {
856 // Zencoder doesn't support this
861 * Reset internal variables to their initial state.
863 public
function reset($keepinput = FALSE
) {
864 parent
::reset($keepinput);
867 unset($this->options
['input']);
869 unset($this->options
['output']);