Fork me on GitHub

Tutorial: Importing Image Set From Files

The code snippets are excerpts (or slightly simplififed ones) from Example_Import_Image_Files.

NOTE: The example script in this page uses any of the following through Gallery Magick Image Processing library:

By default, the script uses the pure Java solution, imgscalr - Java Image-Scaling Library.

In order to let it resize images with non-Java solutions, you MUST install either GraphicsMagick or ImageMagick. Please see the Prerequisites page for detail.

Introduction

Sometimes, image files should be directly imported without any interim JSON files, with creating thumbnail files automatically. This tutorial explains it with an example.

Initializing Import Task

    // 1. Initialize (workflow) document manager and binary importing task using the default implementation.
    def documentManager = new WorkflowDocumentManagerImpl(session)
    def importTask = new DefaultBinaryImportTask(documentManager)
    // 1.1. Sets the logger to the import task, which is useful when running in groovy updater.
    importTask.setLogger(log)

    // 2. Starting import task, meaning the task starts the execution record bookkeeping.
    importTask.start()
          
          

Collect Image Files in Groovy Updater

    // 1. Find all the files having file extensions by the regular expression in the source base VFS folder
    //    at the minimum depth level, 1, and the maximum depth level, 10.
    def sourceBaseFolder = VFS.getManager().resolveFile("file:///var/data/gallery")
    def files = importTask.findFilesByNamePattern(sourceBaseFolder, "^\\w+\\.\\w+\$" , 1, 10)
          
          

Import Binary Content and Create Binary Node

Note: The following example uses GraphicsMagickCommandUtils utility methods provided by Gallery Magick Image Processing library to identify and resize image files by default. GraphicsMagickCommandUtils uses GraphicsMagick commands whereas ImageMagickCommandUtils uses ImageMagick commands intead. Therefore, please replace GraphicsMagickCommandUtils by ImageMagickCommandUtils in the following example if you need to use ImageMagick commands instead of GraphicsMagick commands. See https://bloomreach-forge.github.io/gallery-magickprerequisites.html for more information.

    // 0. Iterate each image file from the collection.
    files.eachWithIndex { file, i ->

      // 1. Determine the target binary handle node path to create or update.
      def binaryLocation = targetBaseFolderNodePath + "/" + file.name.baseName

      // 2. Record instance to store execution status and detail of a unit of migration work item.
      //    these record instances will be collected and summarized when #logSummary() invoked later.
      def record = importTask.beginRecord("", binaryLocation)
      record.setAttribute("file", file.name.path)

      // 3. Mark this unit of import task is being processed before real processing below.
      record.setProcessed(true)

      // 4. Find mimeType from configuration by extension. If not found, skip processing.
      //    Suppose extensionMimeTypes is a map like { "jpg":"image/jpeg", "png":"image/png", "gif":"image/gif", "bmp":"image/bmp", "tiff":"image/tiff" }
      def mimeType = extensionMimeTypes.get(file.name.extension)

      // NOTE: Replace GraphicsMagickCommandUtils by ImageMagickCommandUtils below
      //       if you want to use ImageMagick instead of GraphicsMagick.
      //       ref) https://bloomreach-forge.github.io/gallery-magickprerequisites.html

      // 5. Find the image dimension of source image file.
      def dimension = identifyDimension(file)

      // 6. Create Hippo gallery imageset ContentNode.
      def contentNode = new ContentNode(file.name.baseName, "hippogallery:imageset")
      contentNode.addMixinType("mix:referenceable")
      contentNode.setProperty("hippogallery:description", "Description for " + file.name.baseName)
      contentNode.setProperty("hippogallery:filename", file.name.baseName)

      // 7. Create hippogallery:original child content node and add it to contentNode.
      def originalNode = new ContentNode("hippogallery:original", "hippogallery:image")
      originalNode.setProperty("jcr:mimeType", mimeType)
      originalNode.setProperty("jcr:lastModified", ContentPropertyType.DATE, ISO8601.format(Calendar.getInstance()))
      originalNode.setProperty("hippogallery:width", ContentPropertyType.LONG, "" + dimension.width)
      originalNode.setProperty("hippogallery:height", ContentPropertyType.LONG, "" + dimension.height)
      originalNode.setProperty("jcr:data", ContentPropertyType.BINARY, file.getURL().toString())
      contentNode.addNode(originalNode)

      // 8. Create a thumbnail image file (in JPEG) from the original and store it to a temporary file by using GraphicsMagickCommandUtils.
      def thumbnailFile = ContentFileObjectUtils.createTempFile(file.name.baseName, ".jpg")
      resizeImage(file, thumbnailFile, ImageDimension.from("60x60"))
      // Find the image dimension of thumbnail image file.
      dimension = identifyDimension(thumbnailFile)

      // 9. Create hippogallery:thumbnail child content node and add it to contentNode.
      def thumbnailNode = new ContentNode("hippogallery:thumbnail", "hippogallery:image")
      thumbnailNode.setProperty("jcr:mimeType", "image/jpeg")
      thumbnailNode.setProperty("jcr:lastModified", ContentPropertyType.DATE, ISO8601.format(Calendar.getInstance()))
      thumbnailNode.setProperty("hippogallery:width", ContentPropertyType.LONG, "" + dimension.width)
      thumbnailNode.setProperty("hippogallery:height", ContentPropertyType.LONG, "" + dimension.height)
      thumbnailNode.setProperty("jcr:data", ContentPropertyType.BINARY, thumbnailFile.getURL().toString())
      contentNode.addNode(thumbnailNode)

      // 10. Split target folder path and binary handle node name from the binaryLocation.
      def folderPathAndName = ContentPathUtils.splitToFolderPathAndName(binaryLocation)
      def binaryFolderPath = folderPathAndName[0]
      def binaryName = folderPathAndName[1]
      // Hippo Gallery image folder properties.
      def binaryFolderPrimaryTypeName = "hippogallery:stdImageGallery"
      def binaryFolderFolderTypes = [ "new-image-folder" ] as String[]
      def binaryFolderGalleryTypes = [ "hippogallery:imageset" ] as String[]

      // 11. Make sure that the binary target folder exists or created.
      binaryFolderPath =
          importTask.createOrUpdateBinaryFolder(binaryFolderPath, binaryFolderPrimaryTypeName,
                                                binaryFolderFolderTypes, binaryFolderGalleryTypes)

      // 12. Create or update binary content from contentNode.
      def updatedBinaryLocation =
          importTask.createOrUpdateBinaryFromContentNode(contentNode, binaryPrimaryTypeName,
                                                       binaryFolderPath, binaryName)

      visitorContext.reportUpdated(binaryLocation)
      log.debug "Imported binary from '${file.name.path}' to '${updatedBinaryLocation}'."

      // 13. Mark this unit of import task successful.
      record.setSucceeded(true)

      // 14. End the current execution unit record.
      importTask.endRecord()
    }
          
          

The script shown above uses the following methods in the script itself:

  ImageDimension identifyDimension(FileObject file) {
    // By default, use the pure-Java solution, ScalrProcessorUtils.
    // Optionally, use either GraphicsMagickCommandUtils or ImageMagickCommandUtils.
    def dimension
    if (imageProcessor == "org.onehippo.forge.gallerymagick.core.command.GraphicsMagickCommandUtils") {
      dimension = GraphicsMagickCommandUtils.identifyDimension(ContentFileObjectUtils.toFile(file))
    } else if (imageProcessor == "org.onehippo.forge.gallerymagick.core.command.ImageMagickCommandUtils") {
      dimension = ImageMagickCommandUtils.identifyDimension(ContentFileObjectUtils.toFile(file))
    } else {
      dimension = ScalrProcessorUtils.identifyDimension(ContentFileObjectUtils.toFile(file))
    }
    return dimension
  }

  void resizeImage(FileObject sourceFile, FileObject targetFile, ImageDimension dimension) {
    // By default, use the pure-Java solution, ScalrProcessorUtils.
    // Optionally, use either GraphicsMagickCommandUtils or ImageMagickCommandUtils.
    if (imageProcessor == "org.onehippo.forge.gallerymagick.core.command.GraphicsMagickCommandUtils") {
      GraphicsMagickCommandUtils.resizeImage(ContentFileObjectUtils.toFile(sourceFile), ContentFileObjectUtils.toFile(targetFile), dimension)
    } else if (imageProcessor == "org.onehippo.forge.gallerymagick.core.command.ImageMagickCommandUtils") {
      ImageMagickCommandUtils.resizeImage(ContentFileObjectUtils.toFile(sourceFile), ContentFileObjectUtils.toFile(targetFile), dimension)
    } else {
      ScalrProcessorUtils.resizeImage(ContentFileObjectUtils.toFile(sourceFile), ContentFileObjectUtils.toFile(targetFile), dimension)
    }
  }
          
          

Note: The two methods checks the imageProcessor parameter/member to decide which backend library or command to use in order to identify or resize an image file. By default, it uses the pure Java based solution (org.onehippo.forge.gallerymagick.core.command.ScalrProcessorUtils). You can set the parameter to either org.onehippo.forge.gallerymagick.core.command.GraphicsMagickCommandUtils or org.onehippo.forge.gallerymagick.core.command.ImageMagickCommandUtils to use either non-Java based solution instead.

Log execution summary

    // 1. Stop the import task after processing, which means you stop execution recording.
    importTask.stop()

    // 2. Log the execution summary finally for administrator.
    importTask.logSummary()
          
          

The summary logs above will look like the following:

INFO 2016-02-24 14:02:15 

===============================================================================================================
Execution Summary:
---------------------------------------------------------------------------------------------------------------
Total: 5, Processed: 5, Suceeded: 5, Failed: 0, Duration: 2475ms
---------------------------------------------------------------------------------------------------------------
Details (in CSV format):
---------------------------------------------------------------------------------------------------------------
SEQ,PROCESSED,SUCCEEDED,ID,PATH,TYPE,ATTRIBUTES,ERROR
1,true,true,d26a0531-f035-47ec-827b-e461ee31fce6,/content/gallery/contenteximdemo/imported/hippo.bmp,hippogallery:imageset,{file=/home/test/content-exim-demo-TRUNK/target/tomcat8x/webapps/cms/WEB-INF/data/gallery/hippo.bmp},
2,true,true,abb60b8b-19a3-4c4e-8a3f-1532acf2bf56,/content/gallery/contenteximdemo/imported/hippo.gif,hippogallery:imageset,{file=/home/test/content-exim-demo-TRUNK/target/tomcat8x/webapps/cms/WEB-INF/data/gallery/hippo.gif},
3,true,true,063326fa-08e2-4931-9a90-27b792201a0b,/content/gallery/contenteximdemo/imported/hippo.jpg,hippogallery:imageset,{file=/home/test/content-exim-demo-TRUNK/target/tomcat8x/webapps/cms/WEB-INF/data/gallery/hippo.jpg},
4,true,true,0088f425-8d47-44d6-bf96-b06f23a60dc5,/content/gallery/contenteximdemo/imported/hippo.png,hippogallery:imageset,{file=/home/test/content-exim-demo-TRUNK/target/tomcat8x/webapps/cms/WEB-INF/data/gallery/hippo.png},
5,true,true,3280837f-fd99-4bbd-aeb9-d919ceac6b63,/content/gallery/contenteximdemo/imported/hippo.tiff,hippogallery:imageset,{file=/home/test/content-exim-demo-TRUNK/target/tomcat8x/webapps/cms/WEB-INF/data/gallery/hippo.tiff},
===============================================================================================================