Resize photos below a file size threshold for all silos

Before we were doing it only for Bluesky, but now it's available to
all other silos.
M silorider/commands/process.py +2 -1
@@ 124,7 124,8 @@ class Processor:
             media_callback = silo.mediaCallback
             if self.ctx.args.dry_run:
                 media_callback = silo.dryRunMediaCallback
-            media_ids = upload_silo_media(entry_card, 'photo', media_callback)
+            max_size = getattr(silo, 'PHOTO_LIMIT', None)
+            media_ids = upload_silo_media(entry_card, 'photo', media_callback, max_size)
 
             if not self.ctx.args.dry_run:
                 logger.debug("Posting to '%s': %s" % (silo.name, entry_url))

          
M silorider/silos/base.py +40 -5
@@ 5,6 5,7 @@ import urllib.request
 import logging
 import tempfile
 import mimetypes
+from PIL import Image
 from ..format import format_entry
 
 

          
@@ 160,14 161,14 @@ def load_silos(config, cache):
     return silos
 
 
-def upload_silo_media(card, propname, callback):
+def upload_silo_media(card, propname, callback, max_size=None):
     # The provided callback must take the parameters:
     #  tmpfile path, mimetype, original media url, media description
     with tempfile.TemporaryDirectory(prefix='SiloRider') as tmpdir:
 
         # Upload and use forced image, if any.
         if card.image:
-            mid = _do_upload_silo_media(tmpdir, card.image, None, callback)
+            mid = _do_upload_silo_media(tmpdir, card.image, None, callback, max_size)
             if mid is not None:
                 return [mid]
 

          
@@ 178,14 179,14 @@ def upload_silo_media(card, propname, ca
             media_ids = []
             for media_entry in media_entries:
                 url, desc = _img_url_and_alt(media_entry)
-                mid = _do_upload_silo_media(tmpdir, url, desc, callback)
+                mid = _do_upload_silo_media(tmpdir, url, desc, callback, max_size)
                 if mid is not None:
                     media_ids.append(mid)
 
     return media_ids
 
 
-def _do_upload_silo_media(tmpdir, url, desc, callback):
+def _do_upload_silo_media(tmpdir, url, desc, callback, max_size=None):
     logger.debug("Downloading %s for upload to silo..." % url)
     mt, enc = mimetypes.guess_type(url, strict=False)
     if not mt:

          
@@ 197,14 198,48 @@ def _do_upload_silo_media(tmpdir, url, d
 
     try:
         tmpfile = os.path.join(tmpdir, str(uuid.uuid4()) + ext)
+        logger.debug("Downloading photo to temporary file: %s" % tmpfile)
         tmpfile, headers = urllib.request.urlretrieve(url, filename=tmpfile)
-        logger.debug("Using temporary file: %s" % tmpfile)
+        tmpfile = _ensure_file_not_too_large(tmpfile, max_size)
         return callback(tmpfile, mt, url, desc)
     finally:
         logger.debug("Cleaning up.")
         urllib.request.urlcleanup()
 
 
+def _ensure_file_not_too_large(path, max_size):
+    if max_size is None:
+        return path
+
+    file_size = os.path.getsize(path)
+    if file_size <= max_size:
+        return path
+
+    loops = 0
+    scale = 0.75
+    path_no_ext, ext = os.path.splitext(path)
+    smaller_path = '%s_bsky%s' % (path_no_ext, ext)
+    with Image.open(path) as orig_im:
+        # Resize down 75% until we get below the size limit.
+        img_width, img_height = orig_im.size
+        while loops < 10:
+            logger.debug("Resizing '%s' by a factor of %f" % (path, scale))
+            img_width = int(img_width * scale)
+            img_height = int(img_height * scale)
+            with orig_im.resize((img_width, img_height)) as smaller_im:
+                smaller_im.save(smaller_path)
+
+            file_size = os.path.getsize(smaller_path)
+            logger.debug("Now got file size %d (max size %d)" % (file_size, max_size))
+            if file_size <= max_size:
+                return smaller_path
+
+            scale = scale * scale
+            loops += 1
+
+    raise Exception("Can't reach a small enough image to upload!")
+
+
 def _img_url_and_alt(media_entry):
     # If an image has an alt attribute, the entry comes as a dictionary
     # with 'value' for the url and 'alt' for the description.

          
M silorider/silos/bluesky.py +1 -32
@@ 12,8 12,6 @@ from ..format import CardProps, UrlFlatt
 import atproto
 import atproto.xrpc_client.models as atprotomodels
 
-from PIL import Image
-
 
 logger = logging.getLogger(__name__)
 

          
@@ 52,9 50,9 @@ class _BlueskyClient(atproto.Client):
 
 class BlueskySilo(Silo):
     SILO_TYPE = 'bluesky'
+    PHOTO_LIMIT = 976560
     _DEFAULT_SERVER = 'bsky.app'
     _CLIENT_CLASS = _BlueskyClient
-    _MAX_IMAGE_SIZE = 976560
 
     def __init__(self, ctx):
         super().__init__(ctx)

          
@@ 104,7 102,6 @@ class BlueskySilo(Silo):
         return card
 
     def mediaCallback(self, tmpfile, mt, url, desc):
-        tmpfile = self._ensureFileNotTooLarge(tmpfile)
         with open(tmpfile, 'rb') as tmpfp:
             data = tmpfp.read()
 

          
@@ 116,34 113,6 @@ class BlueskySilo(Silo):
             desc = ""
         return atprotomodels.AppBskyEmbedImages.Image(alt=desc, image=upload.blob)
 
-    def _ensureFileNotTooLarge(self, path):
-        file_size = os.path.getsize(path)
-        if file_size <= self._MAX_IMAGE_SIZE:
-            return path
-
-        loops = 0
-        scale = 0.75
-        path_no_ext, ext = os.path.splitext(path)
-        smaller_path = '%s_bsky%s' % (path_no_ext, ext)
-        with Image.open(path) as orig_im:
-            # Resize down 75% until we get below the size limit.
-            img_width, img_height = orig_im.size
-            while loops < 10:
-                logger.debug("Resizing '%s' by a factor of %f" % (path, scale))
-                img_width = int(img_width * scale)
-                img_height = int(img_height * scale)
-                with orig_im.resize((img_width, img_height)) as smaller_im:
-                    smaller_im.save(smaller_path)
-
-                file_size = os.path.getsize(smaller_path)
-                if file_size <= self._MAX_IMAGE_SIZE:
-                    return smaller_path
-
-                scale = scale * scale
-                loops += 1
-
-        raise Exception("Can't reach a small enough image to upload!")
-
     def postEntry(self, entry_card, media_ids, ctx):
         # Add images as an embed on the atproto record.
         embed = None

          
M silorider/silos/facebook.py +1 -0
@@ 13,6 13,7 @@ logger = logging.getLogger(__name__)
 
 class FacebookSilo(Silo):
     SILO_TYPE = 'facebook'
+    PHOTO_LIMIT = 4000000
     _CLIENT_CLASS = pyfacebook.GraphAPI
 
     def __init__(self, ctx):

          
M silorider/silos/twitter.py +1 -0
@@ 35,6 35,7 @@ class _CompositeClient:
 
 class TwitterSilo(Silo):
     SILO_TYPE = 'twitter'
+    PHOTO_LIMIT = 5000000
     _CLIENT_CLASS = _CompositeClient
 
     def __init__(self, ctx):