import os import re import json import base64 import shutil import qrcode import requests import markdown from markdown.extensions.codehilite import CodeHiliteExtension from markdown.extensions.toc import TocExtension from markdown.extensions.sane_lists import SaneListExtension from markdown.extensions.nl2br import Nl2BrExtension from markdown.extensions.fenced_code import FencedCodeExtension from markdown.extensions.wikilinks import WikiLinkExtension from markdown.extensions.footnotes import FootnoteExtension from markdown.extensions.tables import TableExtension import itertools import frontmatter from io import BytesIO from PIL import ImageOps from utils import Utils from main import Image, ImageProcessor, TextProcessor BITLY_ACCESS_TOKEN = os.getenv('BITLY_ACCESS_TOKEN', None) BITLY_API_URL = "https://api-ssl.bitly.com/v4" class UrlProcessor: def __init__(self, access_token=None, api_url=None): self.access_token = access_token if access_token else BITLY_ACCESS_TOKEN self.api_url = api_url if api_url else BITLY_API_URL def shorten(self, long_url): try: url = self.api_url + "/shorten" headers = { "Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json" } data = { "long_url": long_url } response = requests.post(url, json=data, headers=headers) response.raise_for_status() return response.json().get("id"), response.json() except requests.exceptions.RequestException as e: return None, {"error": str(e)} def generate_qr(self, shorten_url): # DEPRECATED try: qr_url = self.api_url + f"/bitlinks/{shorten_url}/qr" headers = { "Authorization": f"Bearer {self.access_token}", } params = { "customization": { "exclude_bitly_logo": True # TODO } } response = requests.get(qr_url, headers=headers, params=params) response.raise_for_status() qr_code_data = response.json().get("qr_code") qr_code_png_base64 = qr_code_data.split(',')[1] qr_code_png = base64.b64decode(qr_code_png_base64) qr_code_html = f'QR Code' return qr_code_html, qr_code_png, response.json() except requests.exceptions.RequestException as e: return None, None, {"error": str(e)} def generate_qr_local(self, url): try: qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4, ) qr.add_data(url) qr.make(fit=True) img = qr.make_image(fill_color="black", back_color="white") buffered = BytesIO() img.save(buffered, format="PNG") qr_code_png = base64.b64encode(buffered.getvalue()) img_str = qr_code_png.decode('ascii') qr_code_html = f'QR Code' json_str = {"url": url} return qr_code_html, buffered.getvalue(), json_str except Exception as e: return None, None, {"error": str(e)} def shorten_and_generate_qr(access_token, api_url, text, shorten, generate_qr, common_images, individual_images, sx, sy, scale, df): bitly = UrlProcessor(access_token, api_url) index_path = 'index.html' css_path = 'style.css' results = [] ts, tmpd = Utils.get_tempdir() all_json_responses = [] qr_svgs = [] qr_pngs = [] card_htmls = [] card_pngs = [] layer_output_paths = [] # TODO: Rotate Text follows each files? txt = '' if text['files']: for f in text['files']: with open(f) as f: txt += f.read() + '\n\n' if text['text']: txt += text['text'] urls = re.findall(r'http[s]?://\S+', txt) markdown_links = txt if urls is not None: urls = urls if urls is not None else [] for idx, url in enumerate(urls): shorten_url, shorten_response_json = None, None try: if shorten: shorten_url, shorten_response_json = bitly.shorten(url) all_json_responses.append(shorten_response_json) if shorten and shorten_url: url_to_process = shorten_url else: url_to_process = url if generate_qr and url_to_process: if shorten_url: qr_html, qr_png, qr_json = bitly.generate_qr_local(url_to_process) else: qr_html, qr_png, qr_json = bitly.generate_qr_local(url_to_process) if qr_png: os.makedirs(f'{tmpd}/img/qr', exist_ok=True) qr_png_path = f'img/qr/QR{idx:05d}.png' qr_path = os.path.join(tmpd, qr_png_path) with open(qr_path, 'wb') as f: f.write(qr_png) markdown_links = markdown_links.replace( url, f' [{url_to_process}]({url}) ' ) qr_pngs.append(qr_path) qr_svgs.append(qr_html) all_json_responses.append(qr_json) else: results.append(("Error generating QR code", url_to_process)) elif generate_qr: qr_html, qr_png, qr_json = bitly.generate_qr_local(url) if qr_png: os.makedirs(f'{tmpd}/img/qr', exist_ok=True) qr_png_path = f'img/qr/QR{idx:05d}.png' qr_path = os.path.join(tmpd, qr_png_path) with open(qr_path, 'wb') as f: f.write(qr_png) markdown_links = markdown_links.replace( url, f' [{url}]({url}) ' ) qr_pngs.append(qr_path) qr_svgs.append(qr_html) all_json_responses.append(qr_json) else: results.append(("Error generating QR code", url)) elif shorten_url: markdown_links = markdown_links.replace( url, f'[{shorten_url}]({url}) ' ) results.append((None, shorten_url)) else: results.append(("Error shortening URL", url)) except Exception as e: results.append((str(e), url)) size = (int(sx * 3.7795 * scale), int(sy * 3.7795 * scale)) processor = ImageProcessor(size) layer = processor.create_layer() os.makedirs(f'{tmpd}/img/cards', exist_ok=True) if qr_pngs and individual_images: num_iterations = len(qr_pngs) * len(individual_images) elif qr_pngs: num_iterations = len(qr_pngs) elif individual_images: num_iterations = len(individual_images) else: num_iterations = 1 # -------------------content start -------------------------------- index_html = f'\n' processed_text = '' md = markdown.Markdown() for idx, file in enumerate(text['files']): fm = frontmatter.load(file) # TODO replacements = {key: list(item.values())[0] \ for item in fm.metadata['replace'] for key in item} processed_text += TextProcessor.apply_frontmatter(fm.content, replacements) for idx in range(num_iterations): qr_idx = idx % len(qr_pngs) if qr_pngs else 0 indv_idx = idx // len(qr_pngs) if individual_images else 0 qr = qr_pngs[qr_idx] if qr_pngs else None indv = individual_images[indv_idx] if individual_images else None if qr or indv: try: pos_x = int(int(df.loc[idx, 'PosX']) * scale) pos_y = int(int(df.loc[idx, 'PosY']) * scale) pos_px = int(int(df.loc[idx, 'PosPx']) * scale) pos_py = int(int(df.loc[idx, 'PosPy']) * scale) size_x = int(int(df.loc[idx, 'SizeX']) * scale) size_y = int(int(df.loc[idx, 'SizeY']) * scale) size_px = int(int(df.loc[idx, 'SizePx']) * scale) size_py = int(int(df.loc[idx, 'SizePy']) * scale) if common_images: # os.makedirs(f'{tmpd}/img/layer/common', exist_ok=True) for common_img in common_images: layer = processor.combine_images(layer, common_img, size, (0, 0)) # layer_path = os.path.join(tmpd, f'img/layer/common/CM{idx:05d}.png') # layer.save(layer_path, format="PNG") # layer_output_paths.append(layer_path) if indv: # os.makedirs(f'{tmpd}/img/layer/individual', exist_ok=True) layer = processor.combine_images(layer, indv, (size_x, size_y), (pos_x, pos_y)) # layer_path = os.path.join(tmpd, f'img/layer/individual/ID{idx:05d}.png') # layer.save(layer_path, format="PNG") # layer_output_paths.append(layer_path) # if anno_txt: # layer = processor.combine_txt(layer, anno_txt, (size_px, size_py), (pos_px, pos_py)) if qr: # os.makedirs(f'{tmpd}/img/layer/qr', exist_ok=True) layer = processor.combine_images(layer, qr, (size_px, size_py), (pos_px, pos_py)) # layer_path = os.path.join(tmpd, f'img/layer/qr/QR{idx:05d}.png') # layer.save(layer_path, format="PNG") # layer_output_paths.append(layer_path) card_png_path = f'img/cards/CD{idx:05d}.png' card_path = os.path.join(tmpd, card_png_path) layer.save(card_path, format="PNG") card_pngs.append(card_path) with open(card_path, "rb") as image_file: encoded_string = base64.b64encode(image_file.read()).decode() html_txt = md.convert(processed_text) card_html = f'
\n{html_txt}\nCard\n
\n' card_htmls.append(card_html) except Exception as e: raise Exception(f"Error combining images: {e}") for card_html in card_htmls: index_html += card_html index_html += '\n' # -------------------content end -------------------------------- try: json_output_path = os.path.join(tmpd, 'response.json') with open(json_output_path, 'w') as json_file: json.dump(all_json_responses, json_file) markdown_output_path = os.path.join(tmpd, 'index.md') with open(markdown_output_path, 'w') as markdown_file: markdown_file.write(markdown_links) qr_svg_output_paths = [] os.makedirs(f'{tmpd}/chunks/qr', exist_ok=True) for idx, svg in enumerate(qr_svgs): svg_output_path = os.path.join(tmpd, f'chunks/qr/QR{idx:05d}.html') with open(svg_output_path, 'w') as svg_file: svg_file.write(svg) qr_svg_output_paths.append(svg_output_path) qr_png_output_paths = [] for idx, png in enumerate(qr_pngs): qr_png_output_paths.append(png) card_svg_output_paths = [] os.makedirs(f'{tmpd}/chunks/cards', exist_ok=True) for idx, card_html in enumerate(card_htmls): card_output_path = os.path.join(tmpd, f'chunks/cards/CD{idx:05d}.html') with open(card_output_path, 'w') as card_file: card_file.write(card_html) card_svg_output_paths.append(card_output_path) card_png_output_paths = [] for idx, png in enumerate(card_pngs): card_png_output_paths.append(png) index_output_path = os.path.join(tmpd, index_path) with open(index_output_path, 'w') as index_file: index_file.write(index_html) css_output_path = os.path.join(tmpd, css_path) shutil.copy(css_path, css_output_path) zip_output_path = os.path.join(tmpd, f"{ts}.zip") zip_content_list = [ json_output_path, markdown_output_path, *layer_output_paths, *qr_svg_output_paths, *qr_png_output_paths, *card_svg_output_paths, *card_png_output_paths, index_output_path, css_output_path ] zip_output_path = Utils.create_zip(zip_content_list, zip_output_path) except Exception as e: return f"An error occurred: {str(e)}", None, None, [] return markdown_links, card_png_output_paths, zip_output_path, all_json_responses