Fix #1053 - Add height and width attributes and MetadataProcess task
authorJessica Tallon <jessica@megworld.co.uk>
Thu, 11 Dec 2014 11:29:03 +0000 (11:29 +0000)
committerJessica Tallon <jessica@megworld.co.uk>
Thu, 11 Dec 2014 11:52:34 +0000 (11:52 +0000)
    Added "height" and "width" attributes to "image" and "fullImage"
    in the API where possible. The height and width of images wasn't
    being stored anywhere so I've created a task to add or update
    the metadata on images and also started adding those to new images
    when they're submitted in the InitialProcessor.

mediagoblin/db/models.py
mediagoblin/media_types/image/processing.py

index 98a91ccc6a427a5fa9e6dbf7095b0be57a6b90fc..440aa6826a1a9c5cfa14722d9a5ca6f8f4300fbf 100644 (file)
@@ -591,6 +591,22 @@ class MediaEntry(Base, MediaEntryMixin):
                         ),
             }
 
+        # Add image height and width if possible. We didn't use to store this
+        # data and we're not able (and maybe not willing) to re-process all
+        # images so it's possible this might not exist.
+        if self.get_file_metadata("thumb", "height"):
+            height = self.get_file_metadata("thumb", "height")
+            context["image"]["height"] = height
+        if self.get_file_metadata("thumb", "width"):
+            width = self.get_file_metadata("thumb", "width")
+            context["image"]["width"] = width
+        if self.get_file_metadata("original", "height"):
+            height = self.get_file_metadata("original", "height")
+            context["fullImage"]["height"] = height
+        if self.get_file_metadata("original", "height"):
+            width = self.get_file_metadata("original", "width")
+            context["fullImage"]["width"] = width
+
         return context
 
     def unserialize(self, data):
index e0ddfe87705437a940f14545cfc5a6c13c83bebe..951a720d1d5c515234886634a339ed22f027e177 100644 (file)
@@ -225,19 +225,32 @@ class CommonImageProcessor(MediaProcessor):
             self.entry, self.process_filename,
             self.name_builder.fill('{basename}{ext}'))
 
-    def extract_metadata(self):
-        # Is there any GPS data
+    def extract_metadata(self, file):
+        """ Extract all the metadata from the image and store """
+        # Extract GPS data and store in Location
         gps_data = get_gps_data(self.exif_tags)
 
+        if len(gps_data):
+            Location.create({"position": gps_data}, self.entry)
+
         # Insert exif data into database
         exif_all = clean_exif(self.exif_tags)
 
         if len(exif_all):
             self.entry.media_data_init(exif_all=exif_all)
 
-        if len(gps_data):
-            Location.create({"position": gps_data}, self.entry)
-            self.entry.media_data_init(**gps_data)
+        # Extract file metadata
+        try:
+            im = Image.open(self.process_filename)
+        except IOError:
+            raise BadMediaFail()
+
+        metadata = {
+            "width": im.size[0],
+            "height": im.size[1],
+        }
+
+        self.entry.set_file_metadata(file, **metadata)
 
 
 class InitialProcessor(CommonImageProcessor):
@@ -252,6 +265,9 @@ class InitialProcessor(CommonImageProcessor):
         """
         Determine if this media type is eligible for processing
         """
+        if entry is None and state is None:
+            raise ValueError("Must specify either entry or state")
+
         if not state:
             state = entry.state
         return state in (
@@ -301,7 +317,7 @@ class InitialProcessor(CommonImageProcessor):
                                            quality=quality)
         self.generate_thumb(size=thumb_size, filter=filter, quality=quality)
         self.copy_original()
-        self.extract_metadata()
+        self.extract_metadata('original')
         self.delete_queue_file()
 
 
@@ -318,6 +334,9 @@ class Resizer(CommonImageProcessor):
         """
         Determine if this media type is eligible for processing
         """
+        if entry is None and state is None:
+            raise ValueError("Must specify either entry or state")
+
         if not state:
             state = entry.state
         return state in 'processed'
@@ -366,13 +385,52 @@ class Resizer(CommonImageProcessor):
         elif file == 'thumb':
             self.generate_thumb(size=size, filter=filter, quality=quality)
 
+        self.extract_metadata(file)
+
+class MetadataProcessing(CommonImageProcessor):
+    """ Extraction and storage of media's metadata for processed images """
+
+    name = 'metadatabuilder'
+    description = 'Add or update image metadata'
+
+    @classmethod
+    def media_is_eligible(cls, entry=None, state=None):
+        if entry is None and state is None:
+            raise ValueError("Must specify either entry or state")
+
+        if state is None:
+            state = entry.state
+
+        return state == 'processed'
+
+    @classmethod
+    def generate_parser(cls):
+        parser = argparse.ArgumentParser(
+            description=cls.description,
+            prog=cls.name
+        )
+
+        parser.add_argument(
+            'file',
+            choices=['original', 'medium', 'thumb']
+        )
+
+        return parser
+
+    @classmethod
+    def args_to_request(cls, args):
+        return request_from_args(args, ['file'])
+
+    def process(self, file):
+        self.common_setup()
+        self.extract_metadata(file)
 
 class ImageProcessingManager(ProcessingManager):
     def __init__(self):
         super(ImageProcessingManager, self).__init__()
         self.add_processor(InitialProcessor)
         self.add_processor(Resizer)
-
+        self.add_processor(MetadataProcessing)
 
 if __name__ == '__main__':
     import sys