degal/exif.py
branchnew-exif
changeset 105 effae6f38749
parent 104 6afe59e5ffae
child 106 a4f605bd122c
equal deleted inserted replaced
104:6afe59e5ffae 105:effae6f38749
     1 """
     1 """
     2     A custom EXIF parsing module, aimed at high performance.
     2     A custom EXIF parsing module, aimed at high performance.
     3 """
     3 """
     4 
     4 
     5 import struct, mmap, os
     5 import struct, mmap, os
       
     6 
       
     7 from utils import lazy_load, lazy_load_iter
     6 
     8 
     7 def read_struct (file, fmt) :
     9 def read_struct (file, fmt) :
     8     """
    10     """
     9         Utility function to read data from the a file using struct
    11         Utility function to read data from the a file using struct
    10     """
    12     """
   105 class Tag (object) :
   107 class Tag (object) :
   106     """
   108     """
   107         Represents a single Tag in an IFD
   109         Represents a single Tag in an IFD
   108     """
   110     """
   109 
   111 
   110     def __init__ (self, offset, tag, type, count, data_raw) :
   112     def __init__ (self, ifd, offset, tag, type, count, data_raw) :
   111         """
   113         """
   112             Build a Tag with the given binary items from the IFD entry
   114             Build a Tag with the given binary items from the IFD entry
   113         """
   115         """
   114         
   116         
       
   117         self.ifd = ifd
   115         self.offset = offset
   118         self.offset = offset
   116         self.tag = tag
   119         self.tag = tag
   117         self.type = type
   120         self.type = type
   118         self.count = count
   121         self.count = count
   119         self.data_raw = data_raw
   122         self.data_raw = data_raw
   124         # unpack it
   127         # unpack it
   125         if self.type_data :
   128         if self.type_data :
   126             self.type_format, self.type_name, self.type_func = self.type_data
   129             self.type_format, self.type_name, self.type_func = self.type_data
   127     
   130     
   128         # lookup the tag data for this tag
   131         # lookup the tag data for this tag
   129         self.tag_data = exif_data.EXIF_TAGS.get(tag)
   132         self.tag_data = self.ifd.tag_dict.get(tag)
   130         
   133         
   131         # unpack it
       
   132         if self.tag_data :
       
   133             # the EXIF tag name
       
   134             self.tag_name, self.tag_value_spec = self.tag_data
       
   135             
       
   136     @property
   134     @property
   137     def name (self) :
   135     def name (self) :
   138         """
   136         """
   139             Lookup the name of this tag via its code, returns None if unknown.
   137             Lookup the name of this tag via its code, returns None if unknown.
   140         """
   138         """
   141 
   139 
   142         if self.tag_data :
   140         if self.tag_data :
   143             return self.tag_name
   141             return self.tag_data.name
   144 
   142 
   145         else :
   143         else :
   146             return None
   144             return None
   147     
   145     
   148     def process_values (self, raw_values) :
   146     def process_values (self, raw_values) :
   164 
   162 
   165             Returns the comma-separated values by default.
   163             Returns the comma-separated values by default.
   166         """
   164         """
   167 
   165 
   168         if self.tag_data :
   166         if self.tag_data :
   169             spec = self.tag_value_spec
   167             # map it
       
   168             return self.tag_data.map_values(values)
   170 
   169 
   171         else :
   170         else :
   172             # fallback to default
   171             # default value-mapping
   173             spec = None
   172             return ", ".join(str(value) for value in values)
   174 
       
   175         # map it
       
   176         return exif_data.map_values(spec, values)
       
   177 
   173 
   178 # size of an IFD entry in bytes
   174 # size of an IFD entry in bytes
   179 IFD_ENTRY_SIZE = 12
   175 IFD_ENTRY_SIZE = 12
   180 
   176 
   181 class IFD (Buffer) :
   177 class IFD (Buffer) :
   182     """
   178     """
   183         Represents an IFD (Image file directory) region in EXIF data.
   179         Represents an IFD (Image file directory) region in EXIF data.
   184     """
   180     """
   185 
   181 
   186     def __init__ (self, buffer, **buffer_opts) :
   182     def __init__ (self, buffer, tag_dict, **buffer_opts) :
   187         """
   183         """
   188             Access the IFD data from the given bufferable object with given buffer opts.
   184             Access the IFD data from the given bufferable object with given buffer opts.
   189 
   185 
   190             This will read the `count` and `next_offset` values.
   186             This will read the `count` and `next_offset` values.
   191         """
   187         """
   192 
   188 
   193         # init
   189         # init
   194         super(IFD, self).__init__(buffer, **buffer_opts)
   190         super(IFD, self).__init__(buffer, **buffer_opts)
       
   191 
       
   192         # store
       
   193         self.tag_dict = tag_dict
   195         
   194         
   196         # read header
   195         # read header
   197         self.count = self.pread_item(0, 'H')
   196         self.count = self.pread_item(0, 'H')
   198 
   197 
   199         # read next-offset
   198         # read next-offset
   200         self.next_offset = self.pread_item(0x02 + self.count * IFD_ENTRY_SIZE, 'I')
   199         self.next_offset = self.pread_item(0x02 + self.count * IFD_ENTRY_SIZE, 'I')
   201     
   200     
   202     def iter_tags (self) :
   201     @lazy_load_iter
       
   202     def tags (self) :
   203         """
   203         """
   204             Iterate over all the Tag objects in this IFD
   204             Iterate over all the Tag objects in this IFD
   205         """
   205         """
   206         
   206         
   207         # read each tag
   207         # read each tag
   208         for offset in self.iter_offsets(self.count, IFD_ENTRY_SIZE, 0x02) :
   208         for offset in self.iter_offsets(self.count, IFD_ENTRY_SIZE, 0x02) :
   209             # read the tag data
   209             # read the tag data
   210             tag, type, count, data_raw = self.pread_struct(offset, 'HHI4s')
   210             tag, type, count, data_raw = self.pread_struct(offset, 'HHI4s')
   211             
   211             
   212             # yield the new Tag
   212             # yield the new Tag
   213             yield Tag(offset, tag, type, count, data_raw)
   213             yield Tag(self, offset, tag, type, count, data_raw)
   214 
   214 
   215 class EXIF (Buffer) :
   215 class EXIF (Buffer) :
   216     """
   216     """
   217         Represents the EXIF data embedded in some image file in the form of a Region.
   217         Represents the EXIF data embedded in some image file in the form of a Region.
   218     """
   218     """
   228         super(EXIF, self).__init__(buffer, **buffer_opts)
   228         super(EXIF, self).__init__(buffer, **buffer_opts)
   229 
   229 
   230         # store
   230         # store
   231         self.buffer = buffer
   231         self.buffer = buffer
   232     
   232     
   233     def iter_ifds (self) :
   233     @lazy_load_iter
       
   234     def ifds (self) :
   234         """
   235         """
   235             Iterate over the primary IFDs in this EXIF.
   236             Iterate over the primary IFDs in this EXIF.
   236         """
   237         """
   237 
   238 
   238         # starting offset
   239         # starting offset
   239         offset = self.pread_item(0x04, 'I')
   240         offset = self.pread_item(0x04, 'I')
   240 
   241 
   241         while offset :
   242         while offset :
   242             # create and read the IFD, operating on the right sub-buffer
   243             # create and read the IFD, operating on the right sub-buffer
   243             ifd = IFD(self.buf, offset=offset)
   244             ifd = IFD(self.buf, exif_data.EXIF_TAGS, offset=offset)
   244 
   245 
   245             # yield it
   246             # yield it
   246             yield ifd
   247             yield ifd
   247 
   248 
   248             # skip to next offset
   249             # skip to next offset
   250     
   251     
   251     def iter_all_ifds (self) :
   252     def iter_all_ifds (self) :
   252         """
   253         """
   253             Iterate over all of the IFDs contained within this EXIF, or within other IFDs.
   254             Iterate over all of the IFDs contained within this EXIF, or within other IFDs.
   254         """
   255         """
   255 
       
   256     __iter__ = iter_ifds
       
   257     
   256     
   258     def tag_data_info (self, tag) :
   257     def tag_data_info (self, tag) :
   259         """
   258         """
   260             Calculate the location, format and size of the given tag's data.
   259             Calculate the location, format and size of the given tag's data.
   261 
   260 
   498         Dump all tags from the given EXIF object to stdout
   497         Dump all tags from the given EXIF object to stdout
   499     """
   498     """
   500 
   499 
   501     print "EXIF offset=%#08x, size=%d:" % (exif.offset, exif.size)
   500     print "EXIF offset=%#08x, size=%d:" % (exif.offset, exif.size)
   502 
   501 
   503     for i, ifd in enumerate(exif.iter_ifds()) :
   502     for i, ifd in enumerate(exif.ifds) :
   504         print "\tIFD:%d offset=%#04x(%#08x), count=%d, next=%d:" % (
   503         print "\tIFD:%d offset=%#04x(%#08x), count=%d, next=%d:" % (
   505             i, 
   504             i, 
   506             ifd.offset, ifd.offset + exif.offset,
   505             ifd.offset, ifd.offset + exif.offset,
   507             ifd.count, 
   506             ifd.count, 
   508             ifd.next_offset
   507             ifd.next_offset
   509         )
   508         )
   510         
   509         
   511         for i, tag in enumerate(ifd.iter_tags()) :
   510         for i, tag in enumerate(ifd.tags) :
   512             data_info = exif.tag_data_info(tag)
   511             data_info = exif.tag_data_info(tag)
   513 
   512 
   514             if data_info :
   513             if data_info :
   515                 data_fmt, data_offset, data_size = data_info
   514                 data_fmt, data_offset, data_size = data_info
   516 
   515