def get_billing_saldo(self): logger.debug('fetching billing saldo') endpoint = '/billingOnline/accountSaldo' payload = self._do_op('get', endpoint).json() self._validate_model_response(endpoint, payload) return payload['model']
def get_user_info(self): logger.debug('fetching user information') endpoint = '/user/current' payload = self._do_op('get', endpoint).json() self._validate_model_response(endpoint, payload) return payload['model']
def get_quota(self): logger.debug('fetching quota') endpoint = '/user/quota' payload = self._do_op('get', endpoint).json() self._validate_model_response(endpoint, payload) return payload['model']
def _set_svg_page(self, page_number, user_id, card_id, svg_content): logger.debug('set svg template ' + str(page_number) + ' for postcard') endpoint = '/users/{}/mailings/{}/pages/{}'.format( user_id, card_id, page_number) headers = self._get_headers() headers['Origin'] = 'file://' headers['Content-Type'] = 'image/svg+xml' return self._do_op('put', endpoint, data=svg_content, headers=headers)
def send_free_card(self, postcard, mock_send=False, image_export=False, **kwargs): if not postcard: raise PostcardCreatorException('Postcard must be set') postcard.validate() # XXX: endpoint no longer supports user specified w/h kwargs['image_target_width'] = 1819 kwargs['image_quality_factor'] = 1 kwargs['image_target_height'] = 1311 img_base64 = base64.b64encode( rotate_and_scale_image(postcard.picture_stream, img_format='jpeg', image_export=image_export, enforce_size=True, **kwargs)).decode('ascii') img_text_base64 = base64.b64encode( self.create_text_cover(postcard.message)).decode('ascii') endpoint = '/card/upload' payload = { 'lang': 'en', 'paid': False, 'recipient': _format_recipient(postcard.recipient), 'sender': _format_sender(postcard.sender), 'text': '', 'textImage': img_text_base64, # jpeg, JFIF standard 1.01, 720x744 'image': img_base64, # jpeg, JFIF standard 1.01, 1819x1311 'stamp': None } if mock_send: copy = dict(payload) copy['textImage'] = 'omitted' copy['image'] = 'omitted' logger.info( f'mock_send=True, endpoint: {endpoint}, payload: {copy}') return False if not self.has_free_postcard(): raise PostcardCreatorException( 'Limit of free postcards exceeded. Try again tomorrow at ' + self.get_quota()['next']) payload = self._do_op('post', endpoint, json=payload).json() logger.debug(f'{endpoint} with response {payload}') self._validate_model_response(endpoint, payload) logger.info( f'postcard submitted, orderid {payload["model"].get("orderId")}') return payload['model']
def _upload_asset(self, user, card_id, picture_stream): logger.debug('uploading postcard asset') endpoint = '/users/{}/mailings/{}/assets'.format( user["userId"], card_id) files = { 'title': (None, 'Title of image'), 'asset': ('asset.png', picture_stream, 'image/jpeg') } headers = self._get_headers() headers['Origin'] = 'file://' response = self._do_op('post', endpoint, files=files, headers=headers) asset_id = response.headers['Location'].partition('user/')[2] return {'asset_id': asset_id, 'response': response}
def _do_op(self, method, endpoint, **kwargs): url = self.host + endpoint if 'headers' not in kwargs or kwargs['headers'] is None: kwargs['headers'] = self._get_headers() logger.debug('{}: {}'.format(method, url)) response = self._session.request(method, url, **kwargs) _dump_request(response) if response.status_code not in [200, 201, 204]: e = PostcardCreatorException( 'error in request {} {}. status_code: {}'.format( method, url, response.status_code)) e.server_response = response.text raise e return response
def send_free_card(self, postcard, mock_send=False, **kwargs): if not self.has_free_postcard(): raise PostcardCreatorException( 'Limit of free postcards exceeded. Try again tomorrow at ' + self.get_quota()['next']) if not postcard: raise PostcardCreatorException('Postcard must be set') postcard.validate() user = self.get_user_info() user_id = user['userId'] card_id = self._create_card(user) picture_stream = rotate_and_scale_image(postcard.picture_stream, **kwargs) asset_response = self._upload_asset(user, card_id=card_id, picture_stream=picture_stream) self._set_card_recipient(user_id=user_id, card_id=card_id, postcard=postcard) self._set_svg_page( 1, user_id, card_id, self._get_frontpage(asset_id=asset_response['asset_id'])) self._set_svg_page( 2, user_id, card_id, self._get_backpage(postcard.sender, postcard.recipient, postcard.message)) if mock_send: response = False logger.debug('postcard was not sent because flag mock_send=True') else: response = self._do_order(user_id, card_id) logger.debug('postcard sent for printout') return response
def get_quota(self): logger.debug('fetching quota') user = self.get_user_info() endpoint = '/users/{}/quota'.format(user["userId"]) return self._do_op('get', endpoint).json()
def get_billing_saldo(self): logger.debug('fetching billing saldo') user = self.get_user_info() endpoint = '/users/{}/billingOnlineAccountSaldo'.format(user["userId"]) return self._do_op('get', endpoint).json()
def get_user_info(self): logger.debug('fetching user information') endpoint = '/users/current' return self._do_op('get', endpoint).json()
def _do_order(self, user_id, card_id): logger.debug('submit postcard to be printed and delivered') endpoint = '/users/{}/mailings/{}/order'.format(user_id, card_id) return self._do_op('post', endpoint, json={})
def _set_card_recipient(self, user_id, card_id, postcard): logger.debug('set recipient for postcard') endpoint = '/users/{}/mailings/{}/recipients'.format(user_id, card_id) return self._do_op('put', endpoint, json=_format_recipient(postcard.recipient))
def create_text_image(text, image_export=False, **kwargs): """ Create a jpg with given text and return in bytes format """ text_canvas_w = 720 text_canvas_h = 744 text_canvas_bg = 'white' text_canvas_fg = 'black' text_canvas_font_name = 'open_sans_emoji.ttf' def load_font(size): return ImageFont.truetype( pkg_resources.resource_stream(__name__, text_canvas_font_name), size) def find_optimal_size(msg, min_size=20, max_size=400, min_line_w=1, max_line_w=80, padding=0): """ Find optimal font size and line width for a given text """ if min_line_w >= max_line_w: raise Exception( "illegal arguments, min_line_w < max_line_w needed") def line_width(font_size, line_padding=70): l = min_line_w r = max_line_w font = load_font(font_size) while l < r: n = floor((l + r) / 2) t = ''.join([char * n for char in '1']) font_w, font_h = font.getsize(t) font_w = font_w + (2 * line_padding) if font_w >= text_canvas_w: r = n - 1 pass else: l = n + 1 pass return n size_l = min_size size_r = max_size last_line_w = 0 last_size = 0 while size_l < size_r: size = floor((size_l + size_r) / 2.0) last_size = size line_w = line_width(size) last_line_w = line_w lines = [] for line in msg.splitlines(): cur_lines = textwrap.wrap(line, width=line_w) for cur_line in cur_lines: lines.append(cur_line) font = load_font(size) total_w, line_h = font.getsize(msg) tot_height = len(lines) * line_h if tot_height + (2 * padding) < text_canvas_h: start_y = (text_canvas_h - tot_height) / 2 else: start_y = 0 if start_y == 0: size_r = size - 1 else: # does fit size_l = size + 1 return last_size, last_line_w def center_y(lines, font_h): tot_height = len(lines) * font_h if tot_height < text_canvas_h: return (text_canvas_h - tot_height) // 2 else: return 0 size, line_w = find_optimal_size(text, padding=50) logger.debug(f'using font with size: {size}, width: {line_w}') font = load_font(size) font_w, font_h = font.getsize(text) lines = [] for line in text.splitlines(): cur_lines = textwrap.wrap(line, width=line_w) for cur_line in cur_lines: lines.append(cur_line) text_y_start = center_y(lines, font_h) canvas = Image.new('RGB', (text_canvas_w, text_canvas_h), text_canvas_bg) draw = ImageDraw.Draw(canvas) for line in lines: width, height = font.getsize(line) draw.text(((text_canvas_w - width) // 2, text_y_start), line, font=font, fill=text_canvas_fg, embedded_color=True) text_y_start += (height) if image_export: name = strftime("postcard_creator_export_%Y-%m-%d_%H-%M-%S_text.jpg", gmtime()) path = os.path.join(_get_trace_postcard_sent_dir(), name) logger.info('exporting image to {} (image_export=True)'.format(path)) canvas.save(path) img_byte_arr = io.BytesIO() canvas.save(img_byte_arr, format='jpeg') return img_byte_arr.getvalue()
def rotate_and_scale_image( file, image_target_width=154, image_target_height=111, image_quality_factor=20, image_rotate=True, image_export=False, enforce_size=False, # = True, will not make image smaller than given w/h img_format='PNG', **kwargs): with Image.open(file) as image: if image_rotate and image.width < image.height: image = image.rotate(90, expand=True) logger.debug('rotating image by 90 degrees') if not enforce_size and \ (image.width < image_quality_factor * image_target_width or image.height < image_quality_factor * image_target_height): factor_width = image.width / image_target_width factor_height = image.height / image_target_height factor = min([factor_height, factor_width]) logger.debug('image is smaller than default for resize/fill. ' 'using scale factor {} instead of {}'.format( factor, image_quality_factor)) image_quality_factor = factor width = image_target_width * image_quality_factor height = image_target_height * image_quality_factor logger.debug('resizing image from {}x{} to {}x{}'.format( image.width, image.height, width, height)) # XXX: swissid endpoint expect specific size for postcard # if we have an image which is too small, do not upsample but rather center image and fill # with boundary color which is most dominant color in image try: cover = resizeimage.resize_cover(image, [width, height]) except Exception as e: logger.warning(e) logger.warning( f'resizing image from {image.width}x{image.height} to {width}x{height} failed.' f' using resize_contain mode as a fallback. Expect boundaries around img' ) color_thief = ColorThief(file) (r, g, b) = color_thief.get_color(quality=1) color = (r, g, b, 0) cover = resizeimage.resize_contain(image, [width, height], bg_color=color) image_export = True logger.warning( f"using image boundary color {color}, exporting image for visual inspection." ) cover = cover.convert("RGB") with io.BytesIO() as f: cover.save(f, img_format) scaled = f.getvalue() if image_export: name = strftime( "postcard_creator_export_%Y-%m-%d_%H-%M-%S_cover.jpg", gmtime()) path = os.path.join(_get_trace_postcard_sent_dir(), name) logger.info( 'exporting image to {} (image_export=True)'.format(path)) cover.save(path) return scaled