summary refs log tree commit diff
diff options
context:
space:
mode:
authorErik Johnston <erik@matrix.org>2015-07-23 14:12:49 +0100
committerErik Johnston <erik@matrix.org>2015-07-23 14:12:49 +0100
commitff7c2e41de2056ab959a2d560c89d397425c61be (patch)
tree8f626e9b02fbda23340c38fac5c7e1dcd7d12029
parentMerge pull request #205 from matrix-org/erikj/pick_largest_thumbnail (diff)
downloadsynapse-ff7c2e41de2056ab959a2d560c89d397425c61be.tar.xz
Always return a thumbnail of the requested size.
Before, we returned a thumbnail that was at least as big (if possible)
as the requested size. Now, if we don't have a thumbnail of the given
size we generate (and persist) one of that size.
-rw-r--r--synapse/rest/media/v1/base_resource.py83
-rw-r--r--synapse/rest/media/v1/thumbnail_resource.py81
2 files changed, 162 insertions, 2 deletions
diff --git a/synapse/rest/media/v1/base_resource.py b/synapse/rest/media/v1/base_resource.py
index c43ae0314b..00668b3862 100644
--- a/synapse/rest/media/v1/base_resource.py
+++ b/synapse/rest/media/v1/base_resource.py
@@ -226,6 +226,89 @@ class BaseMediaResource(Resource):
             return ()
 
     @defer.inlineCallbacks
+    def _generate_local_exact_thumbnail(self, media_id, t_width, t_height,
+                                        t_method, t_type):
+        input_path = self.filepaths.local_media_filepath(media_id)
+
+        def thumbnail():
+            thumbnailer = Thumbnailer(input_path)
+            m_width = thumbnailer.width
+            m_height = thumbnailer.height
+
+            if m_width * m_height >= self.max_image_pixels:
+                logger.info(
+                    "Image too large to thumbnail %r x %r > %r",
+                    m_width, m_height, self.max_image_pixels
+                )
+                return
+
+            t_path = self.filepaths.local_media_thumbnail(
+                media_id, t_width, t_height, t_type, t_method
+            )
+            self._makedirs(t_path)
+
+            if t_method == "crop":
+                t_len = thumbnailer.crop(t_path, t_width, t_height, t_type)
+            elif t_method == "scale":
+                t_len = thumbnailer.scale(t_path, t_width, t_height, t_type)
+            else:
+                t_len = None
+
+            return t_len, t_path
+
+        res = yield threads.deferToThread(thumbnail)
+
+        if res:
+            t_len, t_path = res
+            yield self.store.store_local_thumbnail(
+                media_id, t_width, t_height, t_type, t_method, t_len
+            )
+
+            defer.returnValue(t_path)
+
+    @defer.inlineCallbacks
+    def _generate_remote_exact_thumbnail(self, server_name, file_id, media_id,
+                                         t_width, t_height, t_method, t_type):
+        input_path = self.filepaths.remote_media_filepath(server_name, file_id)
+
+        def thumbnail():
+            thumbnailer = Thumbnailer(input_path)
+            m_width = thumbnailer.width
+            m_height = thumbnailer.height
+
+            if m_width * m_height >= self.max_image_pixels:
+                logger.info(
+                    "Image too large to thumbnail %r x %r > %r",
+                    m_width, m_height, self.max_image_pixels
+                )
+                return
+
+            t_path = self.filepaths.remote_media_thumbnail(
+                media_id, t_width, t_height, t_type, t_method
+            )
+            self._makedirs(t_path)
+
+            if t_method == "crop":
+                t_len = thumbnailer.crop(t_path, t_width, t_height, t_type)
+            elif t_method == "scale":
+                t_len = thumbnailer.scale(t_path, t_width, t_height, t_type)
+            else:
+                t_len = None
+
+            return t_path, t_len
+
+        res = yield threads.deferToThread(thumbnail)
+
+        if res:
+            t_path, t_len = res
+            yield self.store.store_remote_media_thumbnail(
+                server_name, media_id, file_id,
+                t_width, t_height, t_type, t_method, t_len
+            )
+
+            defer.returnValue(t_path)
+
+    @defer.inlineCallbacks
     def _generate_local_thumbnails(self, media_id, media_info):
         media_type = media_info["media_type"]
         requirements = self._get_thumbnail_requirements(media_type)
diff --git a/synapse/rest/media/v1/thumbnail_resource.py b/synapse/rest/media/v1/thumbnail_resource.py
index 61f88e486e..58621f45df 100644
--- a/synapse/rest/media/v1/thumbnail_resource.py
+++ b/synapse/rest/media/v1/thumbnail_resource.py
@@ -43,11 +43,11 @@ class ThumbnailResource(BaseMediaResource):
         m_type = parse_string(request, "type", "image/png")
 
         if server_name == self.server_name:
-            yield self._respond_local_thumbnail(
+            yield self._select_or_generate_local_thumbnail(
                 request, media_id, width, height, method, m_type
             )
         else:
-            yield self._respond_remote_thumbnail(
+            yield self._select_or_generate_remote_thumbnail(
                 request, server_name, media_id,
                 width, height, method, m_type
             )
@@ -83,6 +83,83 @@ class ThumbnailResource(BaseMediaResource):
             )
 
     @defer.inlineCallbacks
+    def _select_or_generate_local_thumbnail(self, request, media_id, desired_width,
+                                            desired_height, desired_method,
+                                            desired_type):
+        media_info = yield self.store.get_local_media(media_id)
+
+        if not media_info:
+            self._respond_404(request)
+            return
+
+        thumbnail_infos = yield self.store.get_local_media_thumbnails(media_id)
+        for info in thumbnail_infos:
+            t_w = info["thumbnail_width"] == desired_width
+            t_h = info["thumbnail_height"] == desired_height
+            t_method = info["thumbnail_method"] == desired_method
+            t_type = info["thumbnail_type"] == desired_type
+
+            if t_w and t_h and t_method and t_type:
+                file_path = self.filepaths.local_media_thumbnail(
+                    media_id, desired_width, desired_height, desired_type, desired_method,
+                )
+                yield self._respond_with_file(request, desired_type, file_path)
+                return
+
+        logger.debug("We don't have a local thumbnail of that size. Generating")
+
+        # Okay, so we generate one.
+        file_path = yield self._generate_local_exact_thumbnail(
+            media_id, desired_width, desired_height, desired_method, desired_type
+        )
+
+        if file_path:
+            yield self._respond_with_file(request, desired_type, file_path)
+        else:
+            yield self._respond_default_thumbnail(
+                request, media_info, desired_width, desired_height,
+                desired_method, desired_type,
+            )
+
+    @defer.inlineCallbacks
+    def _select_or_generate_remote_thumbnail(self, request, server_name, media_id,
+                                             desired_width, desired_height,
+                                             desired_method, desired_type):
+        media_info = yield self._get_remote_media(server_name, media_id)
+
+        thumbnail_infos = yield self.store.get_remote_media_thumbnails(
+            server_name, media_id,
+        )
+
+        for info in thumbnail_infos:
+            t_w = info["thumbnail_width"] == desired_width
+            t_h = info["thumbnail_height"] == desired_height
+            t_method = info["thumbnail_method"] == desired_method
+            t_type = info["thumbnail_type"] == desired_type
+
+            if t_w and t_h and t_method and t_type:
+                file_path = self.filepaths.remote_media_thumbnail(
+                    media_id, desired_width, desired_height, desired_type, desired_method,
+                )
+                yield self._respond_with_file(request, desired_type, file_path)
+
+        logger.debug("We don't have a local thumbnail of that size. Generating")
+
+        # Okay, so we generate one.
+        path = yield self._generate_remote_exact_thumbnail(
+            server_name, media_id, desired_width, desired_height,
+            desired_method, desired_type
+        )
+
+        if path:
+            yield self._respond_with_file(request, t_type, file_path)
+        else:
+            yield self._respond_default_thumbnail(
+                request, media_info, desired_width, desired_height,
+                desired_method, desired_type,
+            )
+
+    @defer.inlineCallbacks
     def _respond_remote_thumbnail(self, request, server_name, media_id, width,
                                   height, method, m_type):
         # TODO: Don't download the whole remote file