admin commited on
Commit
7c7b7af
1 Parent(s): 5c3d7cf

decomposition

Browse files
requirements.txt CHANGED
@@ -1,3 +1,7 @@
1
  gradio==4.32.2
 
 
 
 
2
  qrcode==7.4.2
3
  Requests==2.32.3
 
1
  gradio==4.32.2
2
+ links==1.0
3
+ Markdown==3.6
4
+ Pillow==10.3.0
5
+ python_frontmatter==1.1.0
6
  qrcode==7.4.2
7
  Requests==2.32.3
src/modules/app.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ from link import UrlProcessor
4
+ # from main import TextProcessor
5
+
6
+
7
+ description = f'''
8
+ You must set the BITLY_ACCESS_TOKEN environment variable.
9
+ For more information, see
10
+ - https://app.bitly.com/settings/api
11
+ '''
12
+
13
+ with gr.Blocks() as demo:
14
+ gr.Markdown(description)
15
+ with gr.Row():
16
+ with gr.Column():
17
+ access_token = gr.Textbox(label="Access Token", placeholder="***")
18
+ api_url = gr.Textbox(label="API base URL", placeholder="https://")
19
+ txt_input = gr.TextArea(label="Enter Text with URLs")
20
+
21
+ with gr.Row():
22
+ shorten_checkbox = gr.Checkbox(label="Shorten URL", value=True)
23
+ qr_checkbox = gr.Checkbox(label="Generate QR Code", value=True)
24
+ submit_button = gr.Button("Process URLs", variant='primary')
25
+
26
+ with gr.Accordion(open=False, label="Insert"):
27
+ with gr.Row():
28
+ cm_img_input = gr.Files(label='Insert common images')
29
+ idv_img_input = gr.Files(label='Insert individual images')
30
+
31
+ with gr.Row():
32
+ pos_x = gr.Number(91, label="Position X", minimum=0, maximum=1000, step=1)
33
+ pos_y = gr.Number(55, label="Position Y", minimum=0, maximum=1000, step=1)
34
+ pos_px = gr.Number(14, label="Position Padding", minimum=0, maximum=1000, step=1)
35
+ pos_py = gr.Number(11, label="Position Padding", minimum=0, maximum=1000, step=1)
36
+
37
+ # positions = [(10, 10), (int(0.9 * s * 3.7795), int(0.9 * 55 * 3.7795))]
38
+ with gr.Column():
39
+ file_output = gr.File(label="Download ZIP")
40
+ with gr.Accordion(open=True, label="Gallery"):
41
+ gallery_output = gr.Gallery(label="PNG Gallery")
42
+ with gr.Accordion(open=False, label="Summary"):
43
+ json_output = gr.JSON(label="JSON Summary")
44
+ with gr.Accordion(open=False, label="Preview"):
45
+ preview_output = gr.TextArea(label="Raw Text")
46
+
47
+ # example_temp_html = TextProcessor.apply_style('demo/demo.md', 'style.css') # TODO
48
+ with open('demo/demo.md') as f:
49
+ demo_txt = f.read()
50
+ examples = [
51
+ ['https://example.com',
52
+ ['demo/demo.jpg'],
53
+ ['demo/demo.jpg']
54
+ ]
55
+ ]
56
+ inputs = [
57
+ txt_input, cm_img_input, idv_img_input
58
+ ]
59
+ gr.Examples(examples, inputs)
60
+
61
+ submit_button.click(
62
+ UrlProcessor.shorten_and_generate_qr,
63
+ inputs=[
64
+ access_token,
65
+ api_url,
66
+ txt_input,
67
+ shorten_checkbox,
68
+ qr_checkbox,
69
+ cm_img_input,
70
+ idv_img_input,
71
+ pos_x,
72
+ pos_y,
73
+ pos_px,
74
+ pos_py,
75
+ ],
76
+ outputs=[preview_output, gallery_output, file_output, json_output]
77
+ )
78
+
79
+ demo.launch()
src/modules/ia-api.py DELETED
@@ -1,164 +0,0 @@
1
- import os
2
- import gradio as gr
3
- import subprocess
4
-
5
- class WaybackAPI:
6
- def __init__(self):
7
- pass
8
-
9
- def configure(self, config_file):
10
- try:
11
- result = subprocess.run(["ia", "configure", "--config-file", config_file], capture_output=True, text=True)
12
- return result.stdout if result.returncode == 0 else result.stderr
13
- except Exception as e:
14
- return str(e)
15
-
16
- def upload(self, identifier, files, urls, metadata):
17
- results = []
18
- if files:
19
- for file in files:
20
- try:
21
- cmd = ["ia", "upload", identifier, file.name] + [f"--metadata={m}" for m in metadata.split(",")]
22
- result = subprocess.run(cmd, capture_output=True, text=True)
23
- results.append((file.name, result.stdout if result.returncode == 0 else result.stderr))
24
- except Exception as e:
25
- results.append((file.name, str(e)))
26
-
27
- if urls:
28
- for url in urls.split(","):
29
- try:
30
- cmd = ["ia", "upload", identifier, url] + [f"--metadata={m}" for m in metadata.split(",")]
31
- result = subprocess.run(cmd, capture_output=True, text=True)
32
- results.append((url, result.stdout if result.returncode == 0 else result.stderr))
33
- except Exception as e:
34
- results.append((url, str(e)))
35
-
36
- return results
37
-
38
- def download(self, identifier, files=None, glob=None, format=None):
39
- try:
40
- cmd = ["ia", "download", identifier]
41
- if files:
42
- cmd += [file.name for file in files]
43
- if glob:
44
- cmd += ["--glob", glob]
45
- if format:
46
- cmd += ["--format", format]
47
- result = subprocess.run(cmd, capture_output=True, text=True)
48
- return result.stdout if result.returncode == 0 else result.stderr
49
- except Exception as e:
50
- return str(e)
51
-
52
- def delete(self, identifier, file=None, cascade=False, all_files=False):
53
- try:
54
- cmd = ["ia", "delete", identifier]
55
- if file:
56
- cmd.append(file)
57
- if cascade:
58
- cmd.append("--cascade")
59
- if all_files:
60
- cmd.append("--all")
61
- result = subprocess.run(cmd, capture_output=True, text=True)
62
- return result.stdout if result.returncode == 0 else result.stderr
63
- except Exception as e:
64
- return str(e)
65
-
66
- def search(self, query, parameters=None, itemlist=False):
67
- try:
68
- cmd = ["ia", "search", query]
69
- if parameters:
70
- cmd += ["--parameters", parameters]
71
- if itemlist:
72
- cmd.append("--itemlist")
73
- result = subprocess.run(cmd, capture_output=True, text=True)
74
- return result.stdout if result.returncode == 0 else result.stderr
75
- except Exception as e:
76
- return str(e)
77
-
78
- def list_files(self, identifier):
79
- try:
80
- result = subprocess.run(["ia", "list", identifier], capture_output=True, text=True)
81
- return result.stdout if result.returncode == 0 else result.stderr
82
- except Exception as e:
83
- return str(e)
84
-
85
- def tasks(self, identifier=None):
86
- try:
87
- cmd = ["ia", "tasks"]
88
- if identifier:
89
- cmd.append(identifier)
90
- result = subprocess.run(cmd, capture_output=True, text=True)
91
- return result.stdout if result.returncode == 0 else result.stderr
92
- except Exception as e:
93
- return str(e)
94
-
95
- wayback_api = WaybackAPI()
96
-
97
- with gr.Blocks() as demo:
98
- with gr.Row():
99
- with gr.Column():
100
- with gr.Accordion("Configure", open=True):
101
- config_file = gr.Textbox(os.getenv('IA_ACCESS_KEY', None), label="API keys")
102
- configure_button = gr.Button("Configure")
103
-
104
- with gr.Accordion("Upload", open=True):
105
- identifier = gr.Textbox(label="Identifier")
106
- files = gr.Files(label="Files")
107
- urls = gr.Textbox(label="URLs (comma separated)")
108
- metadata = gr.Textbox(label="Metadata (comma separated)")
109
- upload_button = gr.Button("Upload")
110
-
111
- with gr.Accordion("Download", open=False):
112
- identifier = gr.Textbox(label="Identifier")
113
- files = gr.Files(label="Files")
114
- glob = gr.Textbox(label="Glob Pattern")
115
- format = gr.Textbox(label="Format")
116
- download_button = gr.Button("Download")
117
-
118
- with gr.Accordion("Delete", open=False):
119
- identifier = gr.Textbox(label="Identifier")
120
- file = gr.Textbox(label="File to delete")
121
- cascade = gr.Checkbox(label="Cascade")
122
- all_files = gr.Checkbox(label="Delete all files")
123
- delete_button = gr.Button("Delete")
124
-
125
- with gr.Accordion("Search", open=False):
126
- query = gr.Textbox(label="Search Query")
127
- parameters = gr.Textbox(label="Parameters")
128
- itemlist = gr.Checkbox(label="Generate Itemlist")
129
- search_button = gr.Button("Search")
130
-
131
- with gr.Accordion("List Files", open=False):
132
- identifier = gr.Textbox(label="Identifier")
133
- list_button = gr.Button("List Files")
134
-
135
- with gr.Accordion("Tasks", open=False):
136
- identifier = gr.Textbox(label="Identifier")
137
- tasks_button = gr.Button("Get Tasks")
138
- gr.Examples([['https://news.yahoo.co.jp/pickup/6502784']], identifier)
139
-
140
- with gr.Column():
141
- configure_output = gr.Textbox(label="Output")
142
- configure_error = gr.Error()
143
- upload_output = gr.Textbox(label="Output")
144
- upload_error = gr.Error()
145
- download_output = gr.Textbox(label="Output")
146
- download_error = gr.Error()
147
- delete_output = gr.Textbox(label="Output")
148
- delete_error = gr.Error()
149
- search_output = gr.Textbox(label="Output")
150
- search_error = gr.Error()
151
- list_output = gr.Textbox(label="Output")
152
- list_error = gr.Error()
153
- tasks_output = gr.Textbox(label="Output")
154
- tasks_error = gr.Error()
155
-
156
- configure_button.click(wayback_api.configure, [config_file], [configure_output, ])
157
- upload_button.click(wayback_api.upload, [identifier, files, urls, metadata], [upload_output, ])
158
- download_button.click(wayback_api.download, [identifier, files, glob, format], [download_output, ])
159
- delete_button.click(wayback_api.delete, [identifier, file, cascade, all_files], [delete_output, ])
160
- search_button.click(wayback_api.search, [query, parameters, itemlist], [search_output, ])
161
- list_button.click(wayback_api.list_files, [identifier], [list_output, ])
162
- tasks_button.click(wayback_api.tasks, [identifier], [tasks_output, ])
163
-
164
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/modules/{bitly-api.min.py → link.py} RENAMED
@@ -1,28 +1,14 @@
1
  import os
 
2
  import re
3
  import base64
4
- import requests
5
  import json
6
- import tempfile
7
- import zipfile
8
- import shutil
9
  import qrcode
10
- from io import BytesIO
11
- import time
12
- import textwrap
13
  import markdown
14
- import frontmatter
15
- from PIL import Image, ImageDraw, ImageFont, ImageOps
16
  from io import BytesIO
17
- import gradio as gr
18
 
19
- description = f'''
20
- You must set the BITLY_ACCESS_TOKEN environment variable.
21
- For more information, see
22
- - https://app.bitly.com/settings/api
23
- - https://dev.bitly.com/api-reference/#createBitlink
24
- - https://dev.bitly.com/api-reference/#createQRCodePublic
25
- '''
26
 
27
 
28
  BITLY_ACCESS_TOKEN = os.getenv('BITLY_ACCESS_TOKEN', None)
@@ -93,20 +79,25 @@ class UrlProcessor:
93
  except Exception as e:
94
  return None, None, {"error": str(e)}
95
 
 
96
  @staticmethod
97
- def shorten_and_generate_qr(access_token, api_url, text, shorten, generate_qr, common_images, individual_images, pos_x, pos_y, pos_p):
98
  bitly = UrlProcessor(access_token, api_url)
99
- urls = re.findall(r'http[s]?://\S+', text)
 
 
100
  results = []
101
- ts, tmpd = Interface.get_tempdir()
102
 
103
- markdown_links = text
104
  all_json_responses = []
105
  qr_svgs = []
106
  qr_pngs = []
107
  card_htmls = []
 
 
108
 
109
- positions = [(pos_p, pos_p), (int(0.9 * pos_x * 3.7795), int(0.9 * pos_y * 3.7795))]
110
 
111
  for idx, url in enumerate(urls):
112
  shorten_url, shorten_response_json = None, None
@@ -127,13 +118,15 @@ class UrlProcessor:
127
  qr_html, qr_png, qr_json = bitly.generate_qr_local(url_to_process)
128
 
129
  if qr_png:
130
- qr_png_path = os.path.join(tmpd, f'qr_code_{idx}.png')
131
- with open(qr_png_path, 'wb') as f:
 
 
132
  f.write(qr_png)
133
  markdown_links = markdown_links.replace(
134
  url, f'<img src="{qr_png_path}" height="40"> [{url_to_process}]({url}) '
135
  )
136
- qr_pngs.append(qr_png_path)
137
  qr_svgs.append(qr_html)
138
  all_json_responses.append(qr_json)
139
  else:
@@ -141,13 +134,15 @@ class UrlProcessor:
141
  elif generate_qr:
142
  qr_html, qr_png, qr_json = bitly.generate_qr_local(url)
143
  if qr_png:
144
- qr_png_path = os.path.join(tmpd, f'qr_code_{idx}.png')
145
- with open(qr_png_path, 'wb') as f:
 
 
146
  f.write(qr_png)
147
  markdown_links = markdown_links.replace(
148
  url, f'<img src="{qr_png_path}" height="40"> [{url}]({url}) '
149
  )
150
- qr_pngs.append(qr_png_path)
151
  qr_svgs.append(qr_html)
152
  all_json_responses.append(qr_json)
153
  else:
@@ -186,13 +181,14 @@ class UrlProcessor:
186
  with open(json_output_path, 'w') as json_file:
187
  json.dump(all_json_responses, json_file)
188
 
189
- markdown_output_path = os.path.join(tmpd, 'links.md')
190
  with open(markdown_output_path, 'w') as markdown_file:
191
  markdown_file.write(markdown_links)
192
 
193
  qr_svg_output_paths = []
 
194
  for idx, svg in enumerate(qr_svgs):
195
- svg_output_path = os.path.join(tmpd, f'qr_code_{idx}.html')
196
  with open(svg_output_path, 'w') as svg_file:
197
  svg_file.write(svg)
198
  qr_svg_output_paths.append(svg_output_path)
@@ -202,18 +198,19 @@ class UrlProcessor:
202
  qr_png_output_paths.append(png)
203
 
204
  card_output_paths = []
 
205
  for idx, card_html in enumerate(card_htmls):
206
- card_output_path = os.path.join(tmpd, f'card_{idx}.html')
207
  with open(card_output_path, 'w') as card_file:
208
  card_file.write(card_html)
209
  card_output_paths.append(card_output_path)
210
 
211
- index_html = '<html><head><link rel="stylesheet" type="text/css" href="style.css"></head><body>'
212
  for card_html in card_htmls:
213
  index_html += card_html
214
  index_html += '</body></html>'
215
 
216
- index_output_path = os.path.join(tmpd, 'index.html')
217
  with open(index_output_path, 'w') as index_file:
218
  index_file.write(index_html)
219
 
@@ -224,211 +221,12 @@ class UrlProcessor:
224
  *qr_svg_output_paths,
225
  *qr_png_output_paths,
226
  *card_output_paths,
227
- index_output_path
 
228
  ]
229
- zip_output_path = Interface.create_zip(zip_content_list, zip_output_path)
230
  except Exception as e:
231
  return f"An error occurred: {str(e)}", None, None, []
232
 
233
  return markdown_links, qr_png_output_paths, zip_output_path, all_json_responses
234
 
235
-
236
- class TextProcessor:
237
- def apply_frontmatter(text, replacements):
238
- for key, value in replacements.items():
239
- text = text.replace(f'{{{key}}}', value)
240
- return text
241
-
242
- def apply_style(input_markdown_path, style_css_path):
243
- with open(input_markdown_path, 'r') as file:
244
- md_content = file.read()
245
-
246
- fm = frontmatter.load(input_markdown_path)
247
- replacements = {key: list(item.values())[0] \
248
- for item in fm.metadata['replace'] for key in item}
249
- processed_text = TextProcessor.apply_frontmatter(md_content, replacements)
250
-
251
- with open(style_css_path, 'r') as file:
252
- style_css = file.read()
253
-
254
- html_content = markdown.markdown(processed_text)
255
- html_content = f'<html><head><style>{style_css}</style></head><body>{html_content}</body></html>'
256
-
257
- temp_html_path = os.path.join(tempfile.gettempdir(), 'processed.html')
258
- with open(temp_html_path, 'w') as file:
259
- file.write(html_content)
260
-
261
- return temp_html_path
262
-
263
- class ImageProcessor:
264
-
265
-
266
- @staticmethod
267
- def create_layer(layout='horizontal', size=(91 * 3.7795, 55 * 3.7795)):
268
- ts, temp_dir = Interface.get_tempdir()
269
- size = (int(size[0]), int(size[1])) # Convert to integers
270
- img = Image.new('RGBA', size, (255, 255, 255, 0))
271
- draw = ImageDraw.Draw(img)
272
- if layout == 'vertical':
273
- draw.rectangle([(0, 0), (size[0], size[1])], outline="lightgray")
274
- elif layout == 'horizontal':
275
- draw.rectangle([(0, 0), (size[0], size[1])], outline="lightgray")
276
-
277
- file_path = os.path.join(temp_dir, 'layer.png')
278
- img.save(file_path, format="PNG")
279
- return file_path
280
-
281
- @staticmethod
282
- def combine_images(base_image_path, overlay_image_paths, positions=None, temp_dir=None):
283
- try:
284
- if temp_dir is None:
285
- _, temp_dir = Interface.get_tempdir()
286
- with open(base_image_path, 'rb') as f:
287
- base_img = Image.open(f).convert("RGBA")
288
- for i, overlay_path in enumerate(overlay_image_paths):
289
- with open(overlay_path, 'rb') as f:
290
- overlay_img = Image.open(f).convert("RGBA")
291
- if positions and i < len(positions):
292
- position = positions[i]
293
- else:
294
- position = (0, 0)
295
- overlay_img_resized = ImageOps.contain(overlay_img, base_img.size)
296
- base_img.paste(overlay_img_resized, position, overlay_img_resized)
297
-
298
- file_path = os.path.join(temp_dir, 'combined.png')
299
- base_img.save(file_path, format="PNG")
300
- return file_path
301
- except Exception as e:
302
- raise Exception(f"Error combining images: {e}")
303
- # @staticmethod
304
- # def insert_txt(png_path, text):
305
- # img = Image.open(png_path)
306
- # draw = ImageDraw.Draw(img)
307
- # font = ImageFont.load_default() # Update to a specific font if needed
308
- # width, height = img.size
309
- # text_width, text_height = draw.textlength(text, font=font)
310
- # text_position = (width - text_width - 10, height - text_height - 10)
311
- # draw.text(text_position, text, font=font, fill="black")
312
-
313
- # with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmpfile:
314
- # img.save(tmpfile.name, format="PNG")
315
- # return tmpfile.name
316
-
317
- # @staticmethod
318
- # def insert_txt(png_path, text):
319
- # img = Image.open(png_path)
320
- # draw = ImageDraw.Draw(img)
321
- # font = ImageFont.load_default() # Update to a specific font if needed
322
- # width, height = img.size
323
- # text_width, text_height = draw.textlength(text, font=font)
324
- # text_position = (width - text_width - 10, height - text_height - 10)
325
- # draw.text(text_position, text, font=font, fill="black")
326
-
327
- # with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmpfile:
328
- # img.save(tmpfile.name, format="PNG")
329
- # return tmpfile.name
330
-
331
- @staticmethod
332
- def move_image(image_path, position, temp_dir=None):
333
- try:
334
- if temp_dir is None:
335
- _, temp_dir = Interface.get_tempdir()
336
- with open(image_path, 'rb') as f:
337
- img = Image.open(f).convert("RGBA")
338
- result_img = Image.new("RGBA", img.size, (255, 255, 255, 0))
339
- result_img.paste(img, position, img)
340
- file_path = os.path.join(temp_dir, 'moved.png')
341
- result_img.save(file_path, format="PNG")
342
- return file_path
343
- except Exception as e:
344
- raise Exception(f"Error moving image: {e}")
345
-
346
- class Interface:
347
- @staticmethod
348
- def get_tempdir():
349
- try:
350
- timestamp = int(time.time())
351
- temp_dir = tempfile.mkdtemp()
352
- return timestamp, temp_dir
353
- except Exception as e:
354
- raise RuntimeError(f"Failed to create temporary directory: {str(e)}")
355
-
356
- @staticmethod
357
- def create_zip(filelist, tmp_fname):
358
- if not filelist:
359
- return None
360
- try:
361
- tmp_dir = os.path.dirname(filelist[0])
362
- zip_name = os.path.join(tmp_dir, tmp_fname)
363
- with zipfile.ZipFile(zip_name, "w") as f:
364
- for file in filelist:
365
- f.write(file, os.path.basename(file))
366
- return zip_name
367
- except Exception as e:
368
- raise RuntimeError(f"Failed to create zip file: {str(e)}")
369
-
370
-
371
- with gr.Blocks() as demo:
372
- gr.Markdown(description)
373
- with gr.Row():
374
- with gr.Column():
375
- access_token = gr.Textbox(label="Access Token", placeholder="***")
376
- api_url = gr.Textbox(label="API base URL", placeholder="https://")
377
- url_input = gr.TextArea(label="Enter Text with URLs")
378
-
379
- with gr.Row():
380
- shorten_checkbox = gr.Checkbox(label="Shorten URL", value=True)
381
- qr_checkbox = gr.Checkbox(label="Generate QR Code", value=True)
382
- submit_button = gr.Button("Process URLs", variant='primary')
383
-
384
- with gr.Accordion(open=False, label="Insert"):
385
- with gr.Row():
386
- cm_img_input = gr.Files(label='Insert common images')
387
- idv_img_input = gr.Files(label='Insert individual images')
388
-
389
- with gr.Row():
390
- pos_x = gr.Number(91, label="Position X", minimum=0, maximum=1000, step=1)
391
- pos_y = gr.Number(55, label="Position Y", minimum=0, maximum=1000, step=1)
392
- pos_p = gr.Number(10, label="Position Padding", minimum=0, maximum=1000, step=1)
393
-
394
- # positions = [(10, 10), (int(0.9 * s * 3.7795), int(0.9 * 55 * 3.7795))]
395
- with gr.Column():
396
- file_output = gr.File(label="Download ZIP")
397
- with gr.Accordion(open=True, label="Gallery"):
398
- gallery_output = gr.Gallery(label="PNG Gallery")
399
- with gr.Accordion(open=False, label="Summary"):
400
- json_output = gr.JSON(label="JSON Summary")
401
- with gr.Accordion(open=False, label="Preview"):
402
- preview_output = gr.TextArea(label="Raw Text")
403
-
404
- example_temp_html = TextProcessor.apply_style('demo.md', 'style.css')
405
-
406
- examples = [
407
- ['http://example.com\nhttp://toscrape.com\nhttp://books.toscrape.com',
408
- ['img/number-1.png'],
409
- ['img/number-2.png']
410
- ]
411
- ]
412
- inputs = [
413
- url_input, cm_img_input, idv_img_input
414
- ]
415
- gr.Examples(examples, inputs)
416
-
417
- submit_button.click(
418
- UrlProcessor.shorten_and_generate_qr,
419
- inputs=[
420
- access_token,
421
- api_url,
422
- url_input,
423
- shorten_checkbox,
424
- qr_checkbox,
425
- cm_img_input,
426
- idv_img_input,
427
- pos_x,
428
- pos_y,
429
- pos_p,
430
- ],
431
- outputs=[preview_output, gallery_output, file_output, json_output]
432
- )
433
-
434
- demo.launch()
 
1
  import os
2
+ import requests
3
  import re
4
  import base64
 
5
  import json
 
 
 
6
  import qrcode
 
 
 
7
  import markdown
 
 
8
  from io import BytesIO
 
9
 
10
+ from utils import Utils
11
+ from main import ImageProcessor
 
 
 
 
 
12
 
13
 
14
  BITLY_ACCESS_TOKEN = os.getenv('BITLY_ACCESS_TOKEN', None)
 
79
  except Exception as e:
80
  return None, None, {"error": str(e)}
81
 
82
+
83
  @staticmethod
84
+ def shorten_and_generate_qr(access_token, api_url, txt, shorten, generate_qr, common_images, individual_images, pos_x, pos_y, pos_px, pos_py):
85
  bitly = UrlProcessor(access_token, api_url)
86
+
87
+ urls = re.findall(r'http[s]?://\S+', txt)
88
+
89
  results = []
90
+ ts, tmpd = Utils.get_tempdir()
91
 
92
+ markdown_links = txt
93
  all_json_responses = []
94
  qr_svgs = []
95
  qr_pngs = []
96
  card_htmls = []
97
+ index_path = 'index.html'
98
+ css_path = 'style.css'
99
 
100
+ positions = [(pos_px, pos_py), (int(0.9 * pos_x * 3.7795), int(0.9 * pos_y * 3.7795))]
101
 
102
  for idx, url in enumerate(urls):
103
  shorten_url, shorten_response_json = None, None
 
118
  qr_html, qr_png, qr_json = bitly.generate_qr_local(url_to_process)
119
 
120
  if qr_png:
121
+ os.makedirs(f'{tmpd}/img/qr', exist_ok=True)
122
+ qr_png_path = f'img/qr/QR{idx:05d}.png'
123
+ qr_path = os.path.join(tmpd, qr_png_path)
124
+ with open(qr_path, 'wb') as f:
125
  f.write(qr_png)
126
  markdown_links = markdown_links.replace(
127
  url, f'<img src="{qr_png_path}" height="40"> [{url_to_process}]({url}) '
128
  )
129
+ qr_pngs.append(qr_path)
130
  qr_svgs.append(qr_html)
131
  all_json_responses.append(qr_json)
132
  else:
 
134
  elif generate_qr:
135
  qr_html, qr_png, qr_json = bitly.generate_qr_local(url)
136
  if qr_png:
137
+ os.makedirs(f'{tmpd}/img/qr', exist_ok=True)
138
+ qr_png_path = f'img/qr/QR{idx:05d}.png'
139
+ qr_path = os.path.join(tmpd, qr_png_path)
140
+ with open(qr_path, 'wb') as f:
141
  f.write(qr_png)
142
  markdown_links = markdown_links.replace(
143
  url, f'<img src="{qr_png_path}" height="40"> [{url}]({url}) '
144
  )
145
+ qr_pngs.append(qr_path)
146
  qr_svgs.append(qr_html)
147
  all_json_responses.append(qr_json)
148
  else:
 
181
  with open(json_output_path, 'w') as json_file:
182
  json.dump(all_json_responses, json_file)
183
 
184
+ markdown_output_path = os.path.join(tmpd, 'index.md')
185
  with open(markdown_output_path, 'w') as markdown_file:
186
  markdown_file.write(markdown_links)
187
 
188
  qr_svg_output_paths = []
189
+ os.makedirs(f'{tmpd}/chunks/qr', exist_ok=True)
190
  for idx, svg in enumerate(qr_svgs):
191
+ svg_output_path = os.path.join(tmpd, f'chunks/qr/QR{idx:05d}.html')
192
  with open(svg_output_path, 'w') as svg_file:
193
  svg_file.write(svg)
194
  qr_svg_output_paths.append(svg_output_path)
 
198
  qr_png_output_paths.append(png)
199
 
200
  card_output_paths = []
201
+ os.makedirs(f'{tmpd}/chunks/cards', exist_ok=True)
202
  for idx, card_html in enumerate(card_htmls):
203
+ card_output_path = os.path.join(tmpd, f'chunks/cards/CD{idx:05d}.html')
204
  with open(card_output_path, 'w') as card_file:
205
  card_file.write(card_html)
206
  card_output_paths.append(card_output_path)
207
 
208
+ index_html = f'<html><head><link rel="stylesheet" type="text/css" href="{css_path}"></head><body>'
209
  for card_html in card_htmls:
210
  index_html += card_html
211
  index_html += '</body></html>'
212
 
213
+ index_output_path = os.path.join(tmpd, index_path)
214
  with open(index_output_path, 'w') as index_file:
215
  index_file.write(index_html)
216
 
 
221
  *qr_svg_output_paths,
222
  *qr_png_output_paths,
223
  *card_output_paths,
224
+ index_output_path,
225
+ css_path
226
  ]
227
+ zip_output_path = Utils.create_zip(zip_content_list, zip_output_path)
228
  except Exception as e:
229
  return f"An error occurred: {str(e)}", None, None, []
230
 
231
  return markdown_links, qr_png_output_paths, zip_output_path, all_json_responses
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/modules/main.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import markdown
3
+ import frontmatter
4
+ from PIL import Image, ImageDraw, ImageFont, ImageOps
5
+
6
+ from utils import Utils
7
+
8
+ class TextProcessor:
9
+ def apply_frontmatter(text, replacements):
10
+ for key, value in replacements.items():
11
+ text = text.replace(f'{{{key}}}', value)
12
+ return text
13
+
14
+ def apply_style(input_markdown_path, style_css_path):
15
+ with open(input_markdown_path, 'r') as file:
16
+ md_content = file.read()
17
+
18
+ fm = frontmatter.load(input_markdown_path)
19
+ replacements = {key: list(item.values())[0] \
20
+ for item in fm.metadata['replace'] for key in item}
21
+ processed_text = TextProcessor.apply_frontmatter(md_content, replacements)
22
+
23
+ with open(style_css_path, 'r') as file:
24
+ style_css = file.read()
25
+
26
+ html_content = markdown.markdown(processed_text)
27
+ html_content = f'<html><head><style>{style_css}</style></head><body>{html_content}</body></html>'
28
+
29
+ temp_html_path = os.path.join(tempfile.gettempdir(), 'processed.html')
30
+ with open(temp_html_path, 'w') as file:
31
+ file.write(html_content)
32
+
33
+ return temp_html_path
34
+
35
+ class ImageProcessor:
36
+
37
+ @staticmethod
38
+ def create_layer(layout='horizontal', size=(91 * 3.7795, 55 * 3.7795)):
39
+ ts, temp_dir = Utils.get_tempdir()
40
+ size = (int(size[0]), int(size[1])) # Convert to integers
41
+ img = Image.new('RGBA', size, (255, 255, 255, 0))
42
+ draw = ImageDraw.Draw(img)
43
+ if layout == 'vertical':
44
+ draw.rectangle([(0, 0), (size[0], size[1])], outline="lightgray")
45
+ elif layout == 'horizontal':
46
+ draw.rectangle([(0, 0), (size[0], size[1])], outline="lightgray")
47
+
48
+ file_path = os.path.join(temp_dir, 'layer.png')
49
+ img.save(file_path, format="PNG")
50
+ return file_path
51
+
52
+ @staticmethod
53
+ def combine_images(base_image_path, overlay_image_paths, positions=None, temp_dir=None):
54
+ try:
55
+ if temp_dir is None:
56
+ _, temp_dir = Utils.get_tempdir()
57
+ with open(base_image_path, 'rb') as f:
58
+ base_img = Image.open(f).convert("RGBA")
59
+ for i, overlay_path in enumerate(overlay_image_paths):
60
+ with open(overlay_path, 'rb') as f:
61
+ overlay_img = Image.open(f).convert("RGBA")
62
+ if positions and i < len(positions):
63
+ position = positions[i]
64
+ else:
65
+ position = (0, 0)
66
+ overlay_img_resized = ImageOps.contain(overlay_img, base_img.size)
67
+ base_img.paste(overlay_img_resized, position, overlay_img_resized)
68
+
69
+ file_path = os.path.join(temp_dir, 'combined.png')
70
+ base_img.save(file_path, format="PNG")
71
+ return file_path
72
+ except Exception as e:
73
+ raise Exception(f"Error combining images: {e}")
74
+ # @staticmethod
75
+ # def insert_txt(png_path, text):
76
+ # img = Image.open(png_path)
77
+ # draw = ImageDraw.Draw(img)
78
+ # font = ImageFont.load_default() # Update to a specific font if needed
79
+ # width, height = img.size
80
+ # text_width, text_height = draw.textlength(text, font=font)
81
+ # text_position = (width - text_width - 10, height - text_height - 10)
82
+ # draw.text(text_position, text, font=font, fill="black")
83
+
84
+ # with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmpfile:
85
+ # img.save(tmpfile.name, format="PNG")
86
+ # return tmpfile.name
87
+
88
+ # @staticmethod
89
+ # def insert_txt(png_path, text):
90
+ # img = Image.open(png_path)
91
+ # draw = ImageDraw.Draw(img)
92
+ # font = ImageFont.load_default() # Update to a specific font if needed
93
+ # width, height = img.size
94
+ # text_width, text_height = draw.textlength(text, font=font)
95
+ # text_position = (width - text_width - 10, height - text_height - 10)
96
+ # draw.text(text_position, text, font=font, fill="black")
97
+
98
+ # with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmpfile:
99
+ # img.save(tmpfile.name, format="PNG")
100
+ # return tmpfile.name
101
+
102
+ @staticmethod
103
+ def move_image(image_path, position, temp_dir=None):
104
+ try:
105
+ if temp_dir is None:
106
+ _, temp_dir = Utils.get_tempdir()
107
+ with open(image_path, 'rb') as f:
108
+ img = Image.open(f).convert("RGBA")
109
+ result_img = Image.new("RGBA", img.size, (255, 255, 255, 0))
110
+ result_img.paste(img, position, img)
111
+ file_path = os.path.join(temp_dir, 'moved.png')
112
+ result_img.save(file_path, format="PNG")
113
+ return file_path
114
+ except Exception as e:
115
+ raise Exception(f"Error moving image: {e}")
116
+
src/modules/mega-api.py DELETED
@@ -1,117 +0,0 @@
1
- import os
2
- import gradio as gr
3
- import subprocess
4
-
5
- class MEGAcmdAPI:
6
- def __init__(self):
7
- pass
8
-
9
- def login(self, email, password):
10
- try:
11
- result = subprocess.run(["mega-login", email, password], capture_output=True, text=True)
12
- return result.stdout if result.returncode == 0 else result.stderr
13
- except Exception as e:
14
- return str(e)
15
-
16
- def logout(self):
17
- try:
18
- result = subprocess.run(["mega-logout"], capture_output=True, text=True)
19
- return result.stdout if result.returncode == 0 else result.stderr
20
- except Exception as e:
21
- return str(e)
22
-
23
- def upload(self, local_path, remote_path):
24
- try:
25
- result = subprocess.run(["mega-put", local_path, remote_path], capture_output=True, text=True)
26
- return result.stdout if result.returncode == 0 else result.stderr
27
- except Exception as e:
28
- return str(e)
29
-
30
- def download(self, remote_path, local_path):
31
- try:
32
- result = subprocess.run(["mega-get", remote_path, local_path], capture_output=True, text=True)
33
- return result.stdout if result.returncode == 0 else result.stderr
34
- except Exception as e:
35
- return str(e)
36
-
37
- def list(self, remote_path):
38
- try:
39
- result = subprocess.run(["mega-ls", remote_path], capture_output=True, text=True)
40
- return result.stdout if result.returncode == 0 else result.stderr
41
- except Exception as e:
42
- return str(e)
43
-
44
- def delete(self, remote_path):
45
- try:
46
- result = subprocess.run(["mega-rm", remote_path], capture_output=True, text=True)
47
- return result.stdout if result.returncode == 0 else result.stderr
48
- except Exception as e:
49
- return str(e)
50
-
51
- def mkdir(self, remote_path):
52
- try:
53
- result = subprocess.run(["mega-mkdir", remote_path], capture_output=True, text=True)
54
- return result.stdout if result.returncode == 0 else result.stderr
55
- except Exception as e:
56
- return str(e)
57
-
58
- mega_api = MEGAcmdAPI()
59
-
60
- with gr.Blocks() as demo:
61
- with gr.Row():
62
- with gr.Column():
63
- with gr.Accordion("Login", open=False):
64
- email = gr.Textbox(label="Email")
65
- password = gr.Textbox(getenv('MEGA_API_KEY'), label="Password", type="password")
66
- login_button = gr.Button("Login")
67
-
68
- with gr.Accordion("Logout", open=False):
69
- logout_button = gr.Button("Logout")
70
-
71
- with gr.Accordion("Upload", open=False):
72
- local_path = gr.Textbox(label="Local Path")
73
- remote_path = gr.Textbox(label="Remote Path")
74
- upload_button = gr.Button("Upload")
75
-
76
- with gr.Accordion("Download", open=False):
77
- remote_path = gr.Textbox(label="Remote Path")
78
- local_path = gr.Textbox(label="Local Path")
79
- download_button = gr.Button("Download")
80
-
81
- with gr.Accordion("List Files", open=False):
82
- remote_path = gr.Textbox(label="Remote Path")
83
- list_button = gr.Button("List Files")
84
-
85
- with gr.Accordion("Delete", open=False):
86
- remote_path = gr.Textbox(label="Remote Path")
87
- delete_button = gr.Button("Delete")
88
-
89
- with gr.Accordion("Create Directory", open=False):
90
- remote_path = gr.Textbox(label="Remote Path")
91
- mkdir_button = gr.Button("Create Directory")
92
-
93
- with gr.Column():
94
- login_output = gr.Textbox(label="Output")
95
- login_error = gr.Error()
96
- logout_output = gr.Textbox(label="Output")
97
- logout_error = gr.Error()
98
- upload_output = gr.Textbox(label="Output")
99
- upload_error = gr.Error()
100
- download_output = gr.Textbox(label="Output")
101
- download_error = gr.Error()
102
- list_output = gr.Textbox(label="Output")
103
- list_error = gr.Error()
104
- delete_output = gr.Textbox(label="Output")
105
- delete_error = gr.Error()
106
- mkdir_output = gr.Textbox(label="Output")
107
- mkdir_error = gr.Error()
108
-
109
- login_button.click(mega_api.login, [email, password], [login_output, ])
110
- logout_button.click(mega_api.logout, [], [logout_output, ])
111
- upload_button.click(mega_api.upload, [local_path, remote_path], [upload_output, ])
112
- download_button.click(mega_api.download, [remote_path, local_path], [download_output, ])
113
- list_button.click(mega_api.list, [remote_path], [list_output, ])
114
- delete_button.click(mega_api.delete, [remote_path], [delete_output, ])
115
- mkdir_button.click(mega_api.mkdir, [remote_path], [mkdir_output, ])
116
-
117
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/modules/utils.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import tempfile
4
+ import zipfile
5
+
6
+
7
+ class Utils:
8
+ @staticmethod
9
+ def get_tempdir():
10
+ try:
11
+ timestamp = int(time.time())
12
+ temp_dir = tempfile.mkdtemp()
13
+ return timestamp, temp_dir
14
+ except Exception as e:
15
+ raise RuntimeError(f"Failed to create temporary directory: {str(e)}")
16
+
17
+ @staticmethod
18
+ def create_zip(filelist, tmp_fname, passwd=None):
19
+ if not filelist:
20
+ return None
21
+ try:
22
+ zip_name = os.path.abspath(tmp_fname)
23
+ with zipfile.ZipFile(zip_name, "w", compression=zipfile.ZIP_DEFLATED) as f:
24
+ for file in filelist:
25
+ if os.path.isfile(file):
26
+ f.write(file, os.path.relpath(file, os.path.dirname(filelist[0])))
27
+ elif os.path.isdir(file):
28
+ for root, dirs, files in os.walk(file):
29
+ for filename in files:
30
+ filepath = os.path.join(root, filename)
31
+ f.write(filepath, os.path.relpath(filepath, os.path.dirname(filelist[0])))
32
+ if passwd:
33
+ zip_name_encrypted = zip_name + ".zip"
34
+ with zipfile.ZipFile(zip_name_encrypted, "w", compression=zipfile.ZIP_DEFLATED) as f:
35
+ f.setpassword(passwd)
36
+ f.write(zip_name, os.path.basename(zip_name))
37
+ os.remove(zip_name)
38
+ return zip_name_encrypted
39
+ else:
40
+ return zip_name
41
+ except Exception as e:
42
+ raise RuntimeError(f"Failed to create zip file: {str(e)}")