Añadir nuevo comentario

Powerpoint presentation to images in PHP: a Drupal example

Difficulty: 
Piece of Cake

In this post I will show you how we implemented a solution that allowed users to upload Powerpoint files to a PHP based web application (Drupal in this example) where they get converted to plain images and rendered back to the user. The requirements for the solution were:

  • Must be self-managed and non dependant on any external tool (such as a service or component installed on another computer or a cloud service).
  • Should not rely on Microsoft Office Interop (see the reasons why here)
  • Should support any Powerpoint version including *.ppt and *.pptx files.
  • The conversion process needs to be fast, such as to be done in the scope of a user request. That means that the conversion process should take no more than 3 to 5 seconds. 
  • The expected workload is that of presentations with between 50 to 150 slides with very high quality images. The application should expect to receive presentations of up to 300Mb in size.
  • We should be able to control the output format, size and quality of the generated image files.
  • We need server side Powerpoint manipulation capabilities - for example - to remove animations from the presentations before exporting to images.

See it in action:

 

The solution

The first thing we need to do is enable the user to upload very big files in a confortable manner. There are serveral options out there, but after our experience implementing the Plupload File Widget module for Drupal 8 we found the PlUpload form element provided by the PlUpload Drupal 8 module to be robust (and tested) enough to be the ideal candidate.

Adding a PlUpload form element is very easy now:

  /**
   * {@inheritdoc}
   */
  public function form(array $form, FormStateInterface $form_state) {
    $form = parent::form($form, $form_state);
    $form = Form::Instance('', $form);

    $form->AddElement('slides', [
      '#type' => 'plupload',
      '#title' => t('Cargar archivos'),
      '#autoupload' => TRUE,
      '#autosubmit' => TRUE,
      '#description' => t('Suba aquí los archivos de slides que se adjuntaran a la presentación.'),
      '#submit_element' => '#slides-upload',
      '#upload_validators' => [
        'file_validate_extensions' => ['zip ppt pptx'],
      ],
      '#plupload_settings' => [
        'runtimes' => 'html5,html4',
        'unique_names' => TRUE,
        'max_file_size' => static::MAX_FILESIZE_UPLOAD,
        'chunk_size' => '1mb',
      ],
    ]);

    $form = $form->Render();

    return $form;
  }

This code will give us a PlUpload form element with client side validation of file extensions:

Now that we have a big file on our server, how do we convert the Powerpoint file into images?

After looking and testing several libraries, the only one that actually worked was the licensed Aspose.SlidesThe library is not cheap but It is well worth the price as it allows you to operate on Powerpoint files as if you where using Office Interop but it is lightning fast and lightweight, and designed to be consumed from your application code (not like Office Interop that is a bridge to an interactive UI based application).

Here is the code to convert the Powerpoint file to a set of images:

  /**
   * Convert a powerpoint file to a collection of PresentacionSlide[]
   * 
   * @param array $file
   * @param \Drupal\envivo\Entity\Presentacion\PresentacionSlide[] $files 
   */
  protected function processFilePowerpoint(array $file, array &$files) {
    /** @var \Drupal\wincachedrupal\NetPhp */
    $netphp = \Drupal::service('netphp');

    $runtime = $netphp->getRuntime();

    $runtime->RegisterAssemblyFromFile("libraries/_bin/aspose/Aspose.Slides.dll", "Aspose.Slides");
    $runtime->RegisterAssemblyFromFullQualifiedName("System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing");

    $destination = strtr(PresentacionSlide::UPLOAD_LOCATION, ['[envivo_presentacion:id]' => $this->entity->id()]);
    file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);

    $sourcefile = drupal_realpath($file['tmppath']);

    $presentation = $runtime->TypeFromName("Aspose.Slides.Presentation")->Instantiate($sourcefile);
    $format = $runtime->TypeFromName("System.Drawing.Imaging.ImageFormat")->Png;

    $x = 0;

    /** @var \NetPhp\Core\NetProxyCollection */
    $slides = $presentation->Slides->AsIterator();

    foreach ($slides as $slide) {
      $x++;
      $bitmap = $slide->GetThumbnail(1, 1);
      $destinationfile = $destination . "\\slide_{$x}.png";
      $bitmap->Save(drupal_realpath($destinationfile), $format);
      $files[] = PresentacionSlide::fromFile($destinationfile);
    }

    $presentation->Dispose();
  }

A few words on the implementation;

  • The NetPhp runtime is retrieved from a service provided by the Drupal 8 Wincache module. If running on Drupal 7 or any other PHP based application you will need to manage and prepare this runtime yourself.
  • We are using the .Net CLR4 runtime and libraries
  • We are using a loose model because this is just a bridge implementation with a couple of lines of code but we could have used a PHP based class model to get intellisense and other IDE specific productivity features working.