Setting EXIF information to Make Android/Google+ Use the 3D Panorama Viewer

Android 4.2 (Jelly Bean) introduces the ability to display equirectangular photos in a special "Photosphere" viewer which allows you to "turn around inside the scene" - in other words, to project part of the pano onto the rectilinear screen. But after creating your own panorama, how can you add EXIF information to allow viewing it in the pano viewer?

The key to making the viewer accept the file as a panorama is setting the right values in the JPEG file's XMP metadata section. A number of editing tools exist for this, for example Alex Masters posted how to do it in Photoshop, and many RAW converters should also be able to create it. I will concentrate on exiftool below, a command-line utility written in Perl.

This is an example of the XMP that gets embedded if you create a pano using the tool that is built into the Android camera app. The full format is described in Panorama XMP Metadata on the Google Developers site.

<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
        xmlns:GPano="http://ns.google.com/photos/1.0/panorama/"
      GPano:UsePanoramaViewer="True"
      GPano:ProjectionType="equirectangular"
      GPano:PoseHeadingDegrees="122.0"
      GPano:CroppedAreaLeftPixels="2092"
      GPano:FullPanoWidthPixels="6322"
      GPano:FirstPhotoDate="2012-11-22T23:15:59.773Z"
      GPano:CroppedAreaImageHeightPixels="1011"
      GPano:FullPanoHeightPixels="3161"
      GPano:SourcePhotosCount="11"
      GPano:CroppedAreaImageWidthPixels="2187"
      GPano:LastPhotoDate="2012-11-22T23:16:41.177Z"
      GPano:CroppedAreaTopPixels="1296"
      GPano:LargestValidInteriorRectLeft="0"
      GPano:LargestValidInteriorRectTop="0"
      GPano:LargestValidInteriorRectWidth="2187"
      GPano:LargestValidInteriorRectHeight="1011"/>
  </rdf:RDF>
</x:xmpmeta>

The names should be self-explanatory: FullPanoWidth/HeightPixels gives the size of the full 360° × 180° pano that the image is a subset of. The JPEG file is 2187 × 1011 pixels large for the above example. Finally, it appears to be possible to restrict the viewer to a crop of the JPEG data with the LargestValidInteriorRect settings.

By default, exiftool can list these tags under Linux, but it will be unable to write them because it is lacking a definition for them. This can be fixed by specifying the definition in your ~/.ExifTool_config file:

%Image::ExifTool::UserDefined = (
    'Image::ExifTool::XMP::Main' => {
        GPano => {
            SubDirectory => {
                TagTable => 'Image::ExifTool::UserDefined::GPano',
            },
        },
    },
);

%Image::ExifTool::UserDefined::GPano = (
    GROUPS => { 0 => 'XMP', 1 => 'XMP-GPano', 2 => 'Image' },
    NAMESPACE => { 'GPano' => 'http://ns.google.com/photos/1.0/panorama/' },
    WRITABLE => 'string',
    UsePanoramaViewer => { Writable => 'string' },
    ProjectionType => { Writable => 'string' },
    PoseHeadingDegrees => { Writable => 'string' },
    CroppedAreaLeftPixels => { Writable => 'string' },
    FullPanoWidthPixels => { Writable => 'string' },
    FirstPhotoDate => { Writable => 'string' },
    CroppedAreaImageHeightPixels => { Writable => 'string' },
    FullPanoHeightPixels => { Writable => 'string' },
    SourcePhotosCount => { Writable => 'string' },
    CroppedAreaImageWidthPixels => { Writable => 'string' },
    LastPhotoDate => { Writable => 'string' },
    CroppedAreaTopPixels => { Writable => 'string' },
    LargestValidInteriorRectLeft => { Writable => 'string' },
    LargestValidInteriorRectTop => { Writable => 'string' },
    LargestValidInteriorRectWidth => { Writable => 'string' },
    LargestValidInteriorRectHeight => { Writable => 'string' },
);

That's all - now you can set these new tags just like any other tags that exiftool knows about! For example, to add meta information for a full 360° × 180° pano which is 8000 × 4000 pixels large, the following command will work:

exiftool -UsePanoramaViewer=True -ProjectionType=equirectangular -PoseHeadingDegrees=180.0 -CroppedAreaLeftPixels=0 -FullPanoWidthPixels=8000 -CroppedAreaImageHeightPixels=4000 -FullPanoHeightPixels=4000 -CroppedAreaImageWidthPixels=8000 -CroppedAreaTopPixels=0 -LargestValidInteriorRectLeft=0 -LargestValidInteriorRectTop=0 -LargestValidInteriorRectWidth=8000 -LargestValidInteriorRectHeight=4000 filename.jpeg

(By the way: The Photosphere app sometimes crashes with such large panoramas.)

exiftool actually sets the values as tags like <UsePanoramaViewer> in the XMP section it adds, instead of XML attributes like in the original Android output above. Luckily, Android also recognizes this format.