How to get thumbnails from PDF How to get thumbnails from PDF

Hi there!

Here's a common question about thumbnail preview in Nuxeo from systemz: How to get thumbnails from PDF. This question also applies to other files, like Office or PSD files.

I will mostly need two things for my thumbnail:

  • somewhere to store it
  • a way to generate it.

For the storage part I will use Mixins. In the Nuxeo Platform, this is just a facet that can be added to a document at runtime. I can add it to a document when I know I can generate a thumbnail from the document's BlobHolder.

The definition of the facet is easy:

[xml]
<extension target="org.nuxeo.ecm.core.schema.TypeService" point="schema">
<schema name="thumbnail" src="schemas/thumbnail.xsd" prefix="thumb" />
</extension>

<extension target="org.nuxeo.ecm.core.schema.TypeService" point="doctype">
<facet name="Thumbnail">
<schema name="thumbnail" />
</facet>
</extension>
[/xml]

Now I have a facet called Thumbnail that adds the thumbnail schema to the document. If you don't know how to add a facet at runtime, it is quite simple:

[java]
myDocumentModel.addFacet("Thumbnail");
[/java]

Then you can simply do:

[java]
doc.setPropertyValue("thumbnail:content", (Serializable) myThumbnailBlob);
[/java]

And to generate my thumbnail, I will use a very simple converter relying on a mighty ImageMagick command. It creates a png thumbnail of any image or file supported by ImageMagick. It can, for instance, take the first page of a PDF or a PSD file.

[xml]
<extension target="org.nuxeo.ecm.platform.commandline.executor.service.CommandLineExecutorComponent"
point="command">
<command name="toThumbnail" enabled="true">
<commandLine>convert</commandLine>
<parameterString>-strip -thumbnail 100x100 -background transparent -gravity center -extent 100x100 -format png -quality 75 #{inputFilePath}[0] #{outputFilePath}</parameterString>
<winParameterString>-strip -thumbnail 100x100 -background transparent -gravity center -extent 100x100 -format png -quality 75 #{inputFilePath}[0] #{outputFilePath}</winParameterString>
<installationDirective>You need to install ImageMagick.</installationDirective>
</command>
</extension>
[/xml]

To call that command, let's use a Converter. This is a simple class implementing the Converter interface.

[java]
package org.nuxeo.thumb;

import java.io.File;
import java.io.Serializable;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
import org.nuxeo.ecm.core.api.impl.blob.FileBlob;
import org.nuxeo.ecm.core.convert.api.ConversionException;
import org.nuxeo.ecm.core.convert.cache.SimpleCachableBlobHolder;
import org.nuxeo.ecm.core.convert.extension.Converter;
import org.nuxeo.ecm.core.convert.extension.ConverterDescriptor;
import org.nuxeo.ecm.core.storage.sql.coremodel.SQLBlob;
import org.nuxeo.ecm.platform.commandline.executor.api.CmdParameters;
import org.nuxeo.ecm.platform.commandline.executor.api.CommandAvailability;
import org.nuxeo.ecm.platform.commandline.executor.api.CommandLineExecutorService;
import org.nuxeo.ecm.platform.commandline.executor.api.ExecResult;
import org.nuxeo.ecm.platform.picture.core.im.IMImageUtils;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.services.streaming.FileSource;
import org.nuxeo.runtime.services.streaming.StreamSource;

/**

  • @author ldoguin
    */
    public class ThumbnailConverter extends IMImageUtils implements Converter {

    private static final Log log = LogFactory.getLog(ThumbnailConverter.class);

    @Override
    public BlobHolder convert(BlobHolder blobHolder,

         Map&lt;String, Serializable&gt; parameters) throws ConversionException {
     try {
         // Make sure the toThumbnail command is available
         CommandLineExecutorService cles = Framework
                 .getLocalService(CommandLineExecutorService.class);
         CommandAvailability commandAvailability = cles
                 .getCommandAvailability(&quot;toThumbnail&quot;);
         if (!commandAvailability.isAvailable()) {
             return null;
         }
         // get the input and output of the command
         Blob blob = blobHolder.getBlob();
         File inputFile = null;
         if (blob instanceof FileBlob) {
             inputFile = ((FileBlob) blob).getFile();
         } else if (blob instanceof SQLBlob) {
             StreamSource source = ((SQLBlob) blob).getBinary()
                     .getStreamSource();
             inputFile = ((FileSource) source).getFile();
         }
         if (inputFile == null) {
             return null;
         }
         CmdParameters params = new CmdParameters();
         File outputFile = File.createTempFile(&quot;nuxeoImageTarget&quot;, &quot;.&quot;
                 + &quot;png&quot;);
         params.addNamedParameter(&quot;inputFilePath&quot;, inputFile);
         params.addNamedParameter(&quot;outputFilePath&quot;, outputFile);
    
         ExecResult res = cles.execCommand(&quot;toThumbnail&quot;, params);
         if (!res.isSuccessful()) {
             return null;
         }
         Blob targetBlob = new FileBlob(outputFile);
         Framework.trackFile(outputFile, targetBlob);
         return new SimpleCachableBlobHolder(targetBlob);
     } catch (Exception e) {
         throw new ConversionException(&quot;Thumbnail conversion has failed&quot;, e);
     }
    

    }

    @Override
    public void init(ConverterDescriptor descriptor) {
    }
    }
    [/java]

As usual, you have to register that class to an extension point.

[xml]
<extension target="org.nuxeo.ecm.core.convert.service.ConversionServiceImpl"
point="converter">
<converter name="toThumbnail" class="org.nuxeo.thumb.ThumbnailConverter">
</converter>
</extension>
[/xml]

Now I can generate and store the thumbnail. However, some questions remain. When will I generate the thumbnail and add it to the document? Where will I display the brand new thumbnail? What if the main Blob of the document changes? I will try to answer these remaining questions next Monday :-) Stay tuned!