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 +1 -0
@@ 35,6 35,7 @@ class _CompositeClient:
class TwitterSilo(Silo):
SILO_TYPE = 'twitter'
PHOTO_LIMIT = 5000000
_CLIENT_CLASS = _CompositeClient
def __init__(self, ctx):