Search

Nautilus - Handle EXIF tags and geolocalisation data

Contents[Hide]

dropcap image geotag

With smartphones or modern digital camera, all digital photos files are now generated with plenty of useful tags.

These tags can be of different type  : EXIF, IPTC or XMP. They can also include some GPS localisation data.

Actually, on a Ubuntu 16.04 workstation, there is no simple way to visualize all these tags  straight from Nautilus file manager when you manage your digital image folders.

This article explains how to extend Nautilus with some simple python extension to :

  • add some columns providing specific image informations (camera model, city, country, GPS data, ...)
  • provide a picture property tab with all picture tags (EXIF, IPTC, XMP, ...)
  • provide a picture property tab with GPS map and address

This procedure has been tested under Ubuntu Gnome 16.04 LTS and Ubuntu 16.04 LTS running Gnome Classic, but it should be applicable to many other modern Gnome Shell based distributions.

Here is what you should get from your Nautilus file manager once you've installed these extensions :

ubuntu nautilus column exif

 

ubuntu nautilus property alltags  ubuntu nautilus property gps

1. Install packages and create environment

Under Ubuntu, Nautilus python extensions are not enabled by default. To enable them, you need to install python-nautilus and python3-gi packages and to create the directory that will hold new Python extensions.

As the extensions will handle EXIF tags, we will also install gir1.2-gexiv2-0.10 python library to help to do the job.

Finally, we will use python-geopy python library to handle address generation from geo-data.

Terminal
# sudo apt-get install python-nautilus python3-gi gir1.2-gexiv2-0.10 python-geopy
# mkdir --parents $HOME/.local/share/nautilus-python/extensions

Python and Nautilus environment are now ready for the new extensions.

2. EXIF Tags Columns Extension

This extension will allow to visualize main picture tags straight from Nautilus when using the list display.

It will provide some new columns that will be populated from picture tags :

  • Camera model
  • City tag
  • Country tag
  • Focal length
  • GPS data availability

ubuntu nautilus column exif

A Nautilus columns provider is a Python class that should provide a least 2 methods :

  • get_columns : called to list the columns provided by the extension
  • update_file_info : called for every file to provide the columns data

The following python script will do the job :

~/.local/share/nautilus-python/extensions/exif-columns.py
#!/usr/bin/python3
# ---------------------------------------------------
# Nautilus extension to add EXIF specific columns
# Procedure :
#   http://bernaerts.dyndns.org/linux/xxx...
#
# Revision history :
#   20/09/2016, V1.0 - Creation by N. Bernaerts
# ---------------------------------------------------

# import libraries
import urllib
import os
import re
import gi
gi.require_version("GExiv2", "0.10")
from gi.repository import GExiv2
gi.require_version("Nautilus", "3.0")
from gi.repository import Nautilus, GObject

# -------------------
# Column extension
# -------------------
class ExifColumnExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider):
  def __init__(self):
    pass
    
  def get_data(self,filename):
    tag_camera = ""
    tag_focal = ""
    tag_city = ""
    tag_country = ""

    # get EXIF metadata
    self.tags = GExiv2.Metadata(filename)

    # read tags from metadata
    if 'Exif.Image.Model' in self.tags: tag_camera = self.tags['Exif.Image.Model']
    if 'Exif.Photo.FocalLengthIn35mmFilm' in self.tags: tag_focal = self.tags['Exif.Photo.FocalLengthIn35mmFilm']
    if 'Xmp.photoshop.City' in self.tags: tag_city = self.tags['Xmp.photoshop.City']
    if 'Xmp.photoshop.Country' in self.tags: tag_country = self.tags['Xmp.photoshop.Country']

    # check if GPS info available
    posGPS = self.tags.get_gps_info()
    if posGPS[0] <> 0 or posGPS[1] <> 0: tag_gps = "yes"
    else: tag_gps = "no"

    return tag_camera, tag_focal, tag_gps, tag_city, tag_country

  def get_columns(self):
      return (
        Nautilus.Column(name="NautilusPython::ExifCameraType", attribute="exif_camera_type", label="Camera", description="Camera model"),
        Nautilus.Column(name="NautilusPython::ExifFocalLength", attribute="exif_focal_length", label="Focal length", description="Focal length (equiv. 35mm)"),
        Nautilus.Column(name="NautilusPython::ExifGPS", attribute="exif_gps", label="GPS", description="Has GPS tags"),
        Nautilus.Column(name="NautilusPython::XMPCity", attribute="xmp_city", label="City", description="City (XMP tag)"),
        Nautilus.Column(name="NautilusPython::XMPCountry", attribute="xmp_country", label="Country", description="Country (XMP tag)"),
      )

  def update_file_info(self, file):
    # if not dealing with file, return
    if file.get_uri_scheme() != 'file':
      return
        
    # read data only if image file
    mimetype = file.get_mime_type().split('/')
    if mimetype[0] in ('image'):
      filename = urllib.unquote(file.get_uri()[7:])
      result = self.get_data(filename)
    else:
      result=("-", "-", "-", "-", "-") 

    # add data to file attributes
    file.add_string_attribute('exif_camera_type', result[0])
    file.add_string_attribute('exif_focal_length', result[1])
    file.add_string_attribute('exif_gps', result[2])
    file.add_string_attribute('xmp_city', result[3])
    file.add_string_attribute('xmp_country', result[4])

To install this extension under ~/.local/share/nautilus-python/extensions/exif-columns.py you just need to run these commands :

Terminal
# wget -O $HOME/.local/share/nautilus-python/extensions/exif-columns.py https://raw.githubusercontent.com/NicolasBernaerts/ubuntu-scripts/master/nautilus/extensions/exif-columns.py
# nautilus -q

You can now add the 5 new digital photo files specific columns. These new columns are sortable like any other column.

3. EXIF Tags property page

This Nautilus extension page provides a new tab in the file property dialog.

It displays a new tab Tags where all available tags of the digital picture file are displayed in alphabetical order.

ubuntu nautilus property alltags

This new tab is provided by the following script :

~/.local/share/nautilus-python/extensions/alltags-properties.py
#!/usr/bin/python3
# ---------------------------------------------------------
# Nautilus extension to display EXIF properties tab
# Procedure :
#   http://bernaerts.dyndns.org/linux/...
#
# Revision history :
#   02/09/2016, V1.0 - Creation by N. Bernaerts
# ---------------------------------------------------

# import libraries
import urllib
import os
import os.path
import re
import pygtk
import gi
gi.require_version("GExiv2", "0.10")
from gi.repository import GExiv2
gi.require_version("Nautilus", "3.0")
from gi.repository import Nautilus, GObject, Gtk

# -------------------
# Property page
# -------------------
class TagsPropertyPage(GObject.GObject, Nautilus.PropertyPageProvider):
  def __init__(self):
    pass

  # method to set one label in the table
  def SetLabel(self, value, row, column, width, align):
    # create label
    labelValue = Gtk.Label()
    labelValue.set_markup(value)

    # set alignment
    if align == "left": labelValue.set_alignment(xalign=0.0, yalign=0.0)
    if align == "center": labelValue.set_alignment(xalign=0.5, yalign=0.0)
    if align == "right": labelValue.set_alignment(xalign=1.0, yalign=0.0)

    # place label
    self.table.attach(labelValue, column, column + width, row, row + 1)
    return

  # method to generate properties tab
  def get_property_pages(self, files):
    # default map size and zoom factor
    sizeMap = '320x320'
    zoomMap = '10'
  
    # if dealing with multiple selection, return
    if len(files) != 1:
      return

    # if not dealing with file, return
    file = files[0]
    if file.get_uri_scheme() != 'file':
      return

    # if mimetype corresponds to JPG image, read data and populate tab
    if file.get_mime_type() in ('image/jpeg' 'image/png'):
    
      # read data from APK file
      filename = urllib.unquote(file.get_uri()[7:])

      # get metadata
      self.tags = GExiv2.Metadata(filename)

      # create table
      self.table = Gtk.Table(len(self.tags), 2)

      # set spacing
      self.table.set_col_spacings(10)
      self.table.set_row_spacings(5)

      # set margins
      self.table.set_margin_start(10)
      self.table.set_margin_end(10)
      self.table.set_margin_top(10)
      self.table.set_margin_bottom(10)

      index = 0
      for (val) in self.tags:
        self.SetLabel("<b>" + val + "</b>", index, 0, 1, "right")
        self.SetLabel(self.tags[val], index, 1, 1, "left")
        index = index + 1

      # set tab content (scrolled window -> table)
      tab_win = Gtk.ScrolledWindow()
      tab_win.add_with_viewport(self.table)
      tab_win.show_all()

      # set tab label
      tab_label = Gtk.Label('Tags')

      # return label and tab content
      return Nautilus.PropertyPage( name="NautilusPython::tags_info", label=tab_label, page=tab_win ),

To install this extension under ~/.local/share/nautilus-python/extensions/exif-alltags.py you just need to run these commands :

Terminal
# wget -O $HOME/.local/share/nautilus-python/extensions/exif-alltags.py https://raw.githubusercontent.com/NicolasBernaerts/ubuntu-scripts/master/nautilus/extensions/exif-alltags.py
# nautilus -q

4. Geolocalisation property page

This Nautilus extension also provides a new tab in the file property dialog.

In case the selected file is having some GPS tags, it displays a new GPS tab where are displayed :

  • longitude, latitude and altitude
  • a map centered on the location
  • the address associated with this location

ubuntu nautilus property gps

Maps are retrieved thru Google Maps static API. URL is built following this model :
https://maps.googleapis.com/maps/api/staticmap?maptype=hybrid&zoom=10&size=400x400&center=longitude,latitude&markers=longitude,latitude

Addresses are generated using Open Street Maps Nominatim service using python-geopy library to access it. Every address is providing an hyperlink that points to Google maps URL centered on the location point.

As of today, both Google Maps and Open Street Maps services are limted in term of number of requests. Google Maps static services are limited to25 000 requests per IP per day and Open Street Maps sets a maximum limit of 1 request per second. To avoid any problem and to limit unneeded queries, all maps pictures and all addresses are cached. Every succesful query is stored under ~/.cache/geotag with a file name including longitude and latitude of the place.

ubuntu nautilus geotag cache

So the new GPS tab is provided by the following script :

~/.local/share/nautilus-python/extensions/exif-geotag.py
#!/usr/bin/python3
# ---------------------------------------------------------
# Nautilus extension to add Geolocalisation properties tab
# Procedure :
#   http://bernaerts.dyndns.org/linux/...
#
# Revision history :
#   30/08/2016, V1.0 - Creation by N. Bernaerts
# ---------------------------------------------------

# import libraries
import urllib
import os
import os.path
import re
import pygtk
import gi
import codecs
gi.require_version("Nautilus", "3.0")
from gi.repository import Nautilus, GObject, Gtk
gi.require_version("GExiv2", "0.10")
from gi.repository import GExiv2
from gi.repository import Gio
from geopy.geocoders import Nominatim

# -------------------
# Property page
# -------------------
class GeotagPropertyPage(GObject.GObject, Nautilus.PropertyPageProvider):
  def __init__(self):
    pass

  # method to set one label in the table
  def SetLabel(self, value, row, column, width, align):
    # create label
    labelValue = Gtk.Label()
    labelValue.set_markup(value)

    # set alignment
    if align == "left": labelValue.set_alignment(xalign=0.0, yalign=0.0)
    if align == "center": labelValue.set_alignment(xalign=0.5, yalign=0.0)
    if align == "right": labelValue.set_alignment(xalign=1.0, yalign=0.0)

    # place label
    self.table.attach(labelValue, column, column + width, row, row + 1)
    return

  # method to set one label in the table
  def SetImage(self, filename, row, column, width, align):
    # create image from jpeg
    imageGeotag = Gtk.Image()
    imageGeotag.set_from_file(filename)

    # set margins
    imageGeotag.set_margin_top(10)
    imageGeotag.set_margin_bottom(5)

    # set alignment
    if align == "left": imageGeotag.set_alignment(xalign=0.0, yalign=0.0)
    if align == "center": imageGeotag.set_alignment(xalign=0.5, yalign=0.0)
    if align == "right": imageGeotag.set_alignment(xalign=1.0, yalign=0.0)

    # place image
    self.table.attach(imageGeotag, column, column + width, row, row + 1)
    return

  # method to generate properties tab
  def get_property_pages(self, files):
    # default map size and zoom factor
    sizeMap = '400x400'
    zoomMap = '8'
  
    # if dealing with multiple selection, return
    if len(files) != 1: return

    # if not dealing with file, return
    file = files[0]
    if file.get_uri_scheme() != 'file': return

    # if mimetype corresponds to JPG image, read data and populate tab
    mimetype = file.get_mime_type().split('/')
    if mimetype[0] in ('image'):
    
      # read data from APK file
      #filename = urllib.unquote(file.get_uri()[7:])
      uri = file.get_uri()
      gvfs = Gio.Vfs.get_default()
      filename = gvfs.get_file_for_uri(uri).get_path()

      # get metadata
      self.tags = GExiv2.Metadata(filename)

      # get GPS position from metadata
      posGPS = self.tags.get_gps_info()
      longitude = posGPS[0]
      latitude = posGPS[1]
      altitude = posGPS[2]

      # if no GPS data, return 
      if latitude == 0 and longitude == 0: return

      # generate GPS position
      strPosition = str(latitude) + ',' + str(longitude)

      # generate Google Maps links
      urlMaps = 'https://www.google.com/maps/place/' + strPosition + '/@' + strPosition + ',' + zoomMap + 'z/'
      urlJpeg = 'https://maps.googleapis.com/maps/api/staticmap?maptype=hybrid&zoom=' + zoomMap + '&size=' + sizeMap + '¢er=' + strPosition + '&markers=' + strPosition

      # generate cache filenames
      dirHomeCache = os.environ['HOME'] + '/.cache'
      dirGeotagCache = os.getenv('XDG_CACHE_HOME', dirHomeCache) + '/geotag'
      fileMap = dirGeotagCache + '/map_' + str(longitude) + '_' + str(latitude) + '_' + sizeMap + '.png'
      fileDesc = dirGeotagCache + '/map_' + str(longitude) + '_' + str(latitude) + '.txt'

      # if cache directory doesn't exist, create it
      if not os.path.exists(dirGeotagCache): os.makedirs(dirGeotagCache)

      # if description is not in the cache, retrieve it from Nominatim
      if not os.path.exists(fileDesc):
        # retrieve place description
        geolocator = Nominatim()
        location = geolocator.reverse(strPosition)
        strDescription = location.address
        strDescription = strDescription[:90]

        # write description to cache
        file = codecs.open(fileDesc, "w", "utf-8")
        file.write(strDescription)
        file.close()
      else:
        # read description from cache
        file = codecs.open(fileDesc, "r", "utf-8")
        strDescription = file.read()
        file.close()

      # if map is not in the cache, retrieve it from Google Maps
      if not os.path.exists(fileMap): urllib.urlretrieve(urlJpeg, fileMap)

      # create table
      self.table = Gtk.Table(4, 3)

      # set spacing
      self.table.set_col_spacings(10)
      self.table.set_row_spacings(5)

      # set margins
      self.table.set_margin_start(10)
      self.table.set_margin_end(10)
      self.table.set_margin_top(10)
      self.table.set_margin_bottom(10)

      # populate table
      self.SetLabel("<b>Longitude</b>", 0, 0, 1, "center")
      self.SetLabel("<b>Latitude</b>", 0, 1, 1, "center")
      self.SetLabel("<b>Altitude</b>", 0, 2, 1, "center")
      self.SetLabel(str(longitude), 1, 0, 1, "center")
      self.SetLabel(str(latitude), 1, 1, 1, "center")
      self.SetLabel(str(altitude), 1, 2, 1, "center")
      self.SetImage(fileMap, 2, 0, 3, "center")
      self.SetLabel("<a href='" + urlMaps + "'>" + strDescription + "</a>", 3, 0, 3, "center")
 
      # set tab content (scrolled window -> table)
      tab_win = Gtk.ScrolledWindow()
      tab_win.add_with_viewport(self.table)
      tab_win.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER)
      tab_win.show_all()

      # set tab label
      tab_label = Gtk.Label('GPS')

      # return label and tab content
      return Nautilus.PropertyPage( name="NautilusPython::geotag_info", label=tab_label, page=tab_win ),

To install this extension under ~/.local/share/nautilus-python/extensions/exif-geotag.py you just need to run these commands :

Terminal
# wget -O $HOME/.local/share/nautilus-python/extensions/exif-geotag.py https://raw.githubusercontent.com/NicolasBernaerts/ubuntu-scripts/master/nautilus/extensions/exif-geotag.py
# nautilus -q

You can now locate your geotagged photos straight from your Nautilus file manager.

Hope it helps.

Signature Technoblog

This article is published "as is", without any warranty that it will work for your specific need.
If you think this article needs some complement, or simply if you think it saved you lots of time & trouble,
just let me know at This email address is being protected from spambots. You need JavaScript enabled to view it.. Cheers !

icon linux icon debian icon apache icon mysql icon php icon piwik icon googleplus