EXIF Metadata Extraction in Scala

by 1/07/2010 09:34:00 AM 3 comments

EXIF Metadata Extraction in Scala

Just for grins I ported the EXIF metadata extraction script in yesterdays post from Groovy to Scala. I ran into a few interesting gotchas while working with Scala.
  • The class TiffConstants inherits a ton of constants (e.g. public static final ints) from other interfaces. However, in scala I had to explicitly use each interface where the constant was defined, using TiffConstants didn't work.
  • There's no Grape for package management. Which is too bad, Scala would be far more useful if there was some equivalent; defining the classpath for a small script is annoying. My work around was just to embed the classpath in the shebang preamble.
  • Got to remember to import implicit List conversions (i.e. import scala.collection.jcl.Conversions._) in order to work with Java Lists as Scala Lists
Here's the code:
#!/bin/sh
exec scala -cp $HOME/.groovy/grapes/org.apache.commons/sanselan/jars/sanselan-0.97.jar $0 $@
!#

import java.io.File
import org.apache.sanselan.{ImageReadException, Sanselan}
import org.apache.sanselan.common.{IImageMetadata, RationalNumber}
import org.apache.sanselan.formats.jpeg.JpegImageMetadata
import org.apache.sanselan.formats.tiff.{TiffField, TiffImageMetadata}
import org.apache.sanselan.formats.tiff.constants.{ExifTagConstants, GPSTagConstants, TagInfo, TiffConstants, TiffTagConstants}
import scala.collection.jcl.Conversions._

val file = new File(args(0))
val metadata = Sanselan.getMetadata(file)
def printTagValue(metadata: JpegImageMetadata, tagInfo: TagInfo): Unit = {
    val field = metadata.findEXIFValue(tagInfo)
    field match {
        case null => println("        (" + tagInfo.name + " not found.)")
        case _ => println("        " + tagInfo.name + ": " + field.getValueDescription())
    }
}

println("file:" + file.getPath())

if (metadata == null) {
    println("\tNo EXIF metadata was found")
}

if (metadata.isInstanceOf[JpegImageMetadata]) {
    val jpegMetadata = metadata.asInstanceOf[JpegImageMetadata]
    
    println("  -- Standard EXIF Tags")
    printTagValue(jpegMetadata,  TiffTagConstants.TIFF_TAG_XRESOLUTION)
    printTagValue(jpegMetadata,  TiffTagConstants.TIFF_TAG_DATE_TIME)
    printTagValue(jpegMetadata,  ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL)
    printTagValue(jpegMetadata,  ExifTagConstants.EXIF_TAG_CREATE_DATE)
    printTagValue(jpegMetadata,  ExifTagConstants.EXIF_TAG_ISO)
    printTagValue(jpegMetadata,  ExifTagConstants.EXIF_TAG_SHUTTER_SPEED_VALUE)
    printTagValue(jpegMetadata,  ExifTagConstants.EXIF_TAG_APERTURE_VALUE)
    printTagValue(jpegMetadata,  ExifTagConstants.EXIF_TAG_BRIGHTNESS_VALUE)
    printTagValue(jpegMetadata,  GPSTagConstants.GPS_TAG_GPS_LATITUDE_REF)
    printTagValue(jpegMetadata,  GPSTagConstants.GPS_TAG_GPS_LATITUDE)
    printTagValue(jpegMetadata,  GPSTagConstants.GPS_TAG_GPS_LONGITUDE_REF)
    printTagValue(jpegMetadata,  GPSTagConstants.GPS_TAG_GPS_LONGITUDE) 
    
    // simple interface to GPS data
    println("  -- GPS Info")
    val exifMetadata = jpegMetadata.getExif()
    if (exifMetadata != null) {
        val gpsInfo = exifMetadata.getGPS()   
        if (gpsInfo != null) {
            val longitude = gpsInfo.getLongitudeAsDegreesEast()
            val latitude = gpsInfo.getLatitudeAsDegreesNorth()
            
            println("        GPS Description: " + gpsInfo)
            println("        GPS Longitude (Degrees East): " + longitude)
            println("        GPS Latitude (Degrees North): " + latitude)
        }
    }
    
    println("  -- All EXIF info")
    jpegMetadata.getItems().foreach(item => println("        " + item))
    println("")
}

Brian Schlining

Developer

Cras justo odio, dapibus ac facilisis in, egestas eget quam. Curabitur blandit tempus porttitor. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.

3 comments:

leonidv said...

Do you know, that grape is used maven (or Ivi, I'm not sure) repository for getting artifacts?
Really, it's very comfortable in small scripts (like your example), but in big project it's easy to configure all dependency in one place (pom.xml).

I think that Groovy is excellent for small and medium projects and Scala is great for huge enterprise projects.

Hohonuuli said...

@leonidv
Grape, by default, uses Ivy but stores the artifact in $HOME/.groovy/grapes. However, you can modify $HOME/.groovy/grapesConfig.xml so that it looks in local and remote maven repositories for the artifact. Here's an example grapesConfig.xml file that does that:

<ivysettings>
<settings defaultResolver="downloadGrapes"/>
<resolvers>
<chain name="downloadGrapes">

<ibiblio name="local" root="file:${user.home}/.m2/repository/" m2compatible="true"/>
<filesystem name="cachedGrapes">
<ivy pattern="${user.home}/.groovy/grapes/[organisation]/[module]/ivy-[revision].xml"/>
<artifact pattern="${user.home}/.groovy/grapes/[organisation]/[module]/[type]s/[artifact]-[revision].[ext]"/>
</filesystem>
<ibiblio name="codehaus" root="http://repository.codehaus.org/" m2compatible="true"/>
<ibiblio name="ibiblio" m2compatible="true"/>
<ibiblio name="java.net2" root="http://download.java.net/maven/2/" m2compatible="true"/>
</chain>
</resolvers>
</ivysettings>

Hohonuuli said...

@leonidv

I agree with you about configuring dependencies in the pom.xml as the best choice. For one-off scripts though, it's hard to beat the convenience of Grape.