awacke1 commited on
Commit
01216ba
Β·
verified Β·
1 Parent(s): 646f19c

Update backup7.app.py

Browse files
Files changed (1) hide show
  1. backup7.app.py +157 -6
backup7.app.py CHANGED
@@ -20,13 +20,17 @@ import pytz
20
 
21
  st.set_page_config(layout="wide", initial_sidebar_state="collapsed")
22
 
 
23
  def get_timestamp_prefix():
 
24
  central = pytz.timezone("US/Central")
25
  now = datetime.now(central)
26
  # Format: three-letter day, MMDD, HHMM + AM/PM (all uppercase)
27
  return now.strftime("%a %m%d %I%M%p").upper()
28
 
 
29
  def clean_for_speech(text):
 
30
  # Remove hash marks
31
  text = text.replace("#", "")
32
  # Remove emojis using a regex pattern that covers a wide range
@@ -45,12 +49,86 @@ def clean_for_speech(text):
45
  text = emoji_pattern.sub('', text)
46
  return text
47
 
 
48
  async def generate_audio(text, voice, filename):
 
49
  communicate = edge_tts.Communicate(text, voice)
50
  await communicate.save(filename)
51
  return filename
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  def apply_emoji_font(text, emoji_font):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  emoji_pattern = re.compile(
55
  r"([\U0001F300-\U0001F5FF"
56
  r"\U0001F600-\U0001F64F"
@@ -65,40 +143,89 @@ def apply_emoji_font(text, emoji_font):
65
  r"\u2600-\u26FF"
66
  r"\u2700-\u27BF]+)"
67
  )
 
68
  def replace_emoji(match):
69
  emoji = match.group(1)
70
  emoji = unicodedata.normalize('NFC', emoji)
71
  return f'<font face="{emoji_font}">{emoji}</font>'
 
72
  segments = []
73
  last_pos = 0
 
74
  for match in emoji_pattern.finditer(text):
75
  start, end = match.span()
76
  if last_pos < start:
77
  segments.append(f'<font face="DejaVuSans">{text[last_pos:start]}</font>')
78
  segments.append(replace_emoji(match))
79
  last_pos = end
 
80
  if last_pos < len(text):
81
  segments.append(f'<font face="DejaVuSans">{text[last_pos:]}</font>')
82
- return ''.join(segments)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
 
84
  def markdown_to_pdf_content(markdown_text, render_with_bold, auto_bold_numbers):
 
85
  lines = markdown_text.strip().split('\n')
86
  pdf_content = []
87
  number_pattern = re.compile(r'^\d+\.\s')
 
88
  for line in lines:
89
  line = line.strip()
90
  if not line or line.startswith('# '):
91
  continue
 
 
 
 
 
92
  if render_with_bold:
93
  line = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', line)
 
94
  if auto_bold_numbers and number_pattern.match(line):
 
95
  if not (line.startswith("<b>") and line.endswith("</b>")):
96
- line = f"<b>{line}</b>"
 
 
 
 
 
 
 
 
97
  pdf_content.append(line)
 
98
  total_lines = len(pdf_content)
99
  return pdf_content, total_lines
100
 
 
101
  def create_pdf(markdown_text, base_font_size, render_with_bold, auto_bold_numbers, enlarge_numbered, num_columns):
 
102
  buffer = io.BytesIO()
103
  page_width = A4[0] * 2
104
  page_height = A4[1]
@@ -106,19 +233,26 @@ def create_pdf(markdown_text, base_font_size, render_with_bold, auto_bold_number
106
  styles = getSampleStyleSheet()
107
  spacer_height = 10
108
  pdf_content, total_lines = markdown_to_pdf_content(markdown_text, render_with_bold, auto_bold_numbers)
 
 
109
  item_style = ParagraphStyle(
110
  'ItemStyle', parent=styles['Normal'], fontName="DejaVuSans",
111
- fontSize=base_font_size, leading=base_font_size * 1.15, spaceAfter=1
 
112
  )
113
  numbered_bold_style = ParagraphStyle(
114
  'NumberedBoldStyle', parent=styles['Normal'], fontName="NotoEmoji-Bold",
115
  fontSize=base_font_size + 1 if enlarge_numbered else base_font_size,
116
- leading=(base_font_size + 1) * 1.15 if enlarge_numbered else base_font_size * 1.15, spaceAfter=1
 
117
  )
118
  section_style = ParagraphStyle(
119
  'SectionStyle', parent=styles['Heading2'], fontName="DejaVuSans",
120
- textColor=colors.darkblue, fontSize=base_font_size * 1.1, leading=base_font_size * 1.32, spaceAfter=2
 
121
  )
 
 
122
  try:
123
  available_font_files = glob.glob("*.ttf")
124
  if not available_font_files:
@@ -135,17 +269,22 @@ def create_pdf(markdown_text, base_font_size, render_with_bold, auto_bold_number
135
  except Exception as e:
136
  st.error(f"Font registration error: {e}")
137
  return
 
 
138
  columns = [[] for _ in range(num_columns)]
139
  lines_per_column = total_lines / num_columns if num_columns > 0 else total_lines
140
  current_line_count = 0
141
  current_column = 0
142
  number_pattern = re.compile(r'^\d+\.\s')
 
143
  for item in pdf_content:
144
  if current_line_count >= lines_per_column and current_column < num_columns - 1:
145
  current_column += 1
146
  current_line_count = 0
147
  columns[current_column].append(item)
148
  current_line_count += 1
 
 
149
  column_cells = [[] for _ in range(num_columns)]
150
  for col_idx, column in enumerate(columns):
151
  for item in column:
@@ -157,9 +296,13 @@ def create_pdf(markdown_text, base_font_size, render_with_bold, auto_bold_number
157
  column_cells[col_idx].append(Paragraph(apply_emoji_font(content, "NotoEmoji-Bold"), section_style))
158
  else:
159
  column_cells[col_idx].append(Paragraph(apply_emoji_font(item, "DejaVuSans"), item_style))
 
 
160
  max_cells = max(len(cells) for cells in column_cells) if column_cells else 0
161
  for cells in column_cells:
162
  cells.extend([Paragraph("", item_style)] * (max_cells - len(cells)))
 
 
163
  col_width = (page_width - 72) / num_columns if num_columns > 0 else page_width - 72
164
  table_data = list(zip(*column_cells)) if column_cells else [[]]
165
  table = Table(table_data, colWidths=[col_width] * num_columns, hAlign='CENTER')
@@ -174,12 +317,16 @@ def create_pdf(markdown_text, base_font_size, render_with_bold, auto_bold_number
174
  ('TOPPADDING', (0, 0), (-1, -1), 1),
175
  ('BOTTOMPADDING', (0, 0), (-1, -1), 1),
176
  ]))
 
 
177
  story = [Spacer(1, spacer_height), table]
178
  doc.build(story)
179
  buffer.seek(0)
180
  return buffer.getvalue()
181
 
 
182
  def pdf_to_image(pdf_bytes):
 
183
  try:
184
  doc = fitz.open(stream=pdf_bytes, filetype="pdf")
185
  images = []
@@ -197,6 +344,7 @@ def pdf_to_image(pdf_bytes):
197
  md_files = [f for f in glob.glob("*.md") if os.path.basename(f) != "README.md"]
198
  md_options = [os.path.splitext(os.path.basename(f))[0] for f in md_files]
199
 
 
200
  with st.sidebar:
201
  st.markdown("### PDF Options")
202
  if md_options:
@@ -256,9 +404,11 @@ with st.sidebar:
256
  mime="audio/mpeg"
257
  )
258
 
 
259
  with st.spinner("Generating PDF..."):
260
  pdf_bytes = create_pdf(st.session_state.markdown_content, base_font_size, render_with_bold, auto_bold_numbers, enlarge_numbered, num_columns)
261
 
 
262
  with st.container():
263
  pdf_images = pdf_to_image(pdf_bytes)
264
  if pdf_images:
@@ -267,10 +417,11 @@ with st.container():
267
  else:
268
  st.info("Download the PDF to view it locally.")
269
 
 
270
  with st.sidebar:
271
  st.download_button(
272
  label="πŸ’ΎπŸ“„ Save PDF",
273
  data=pdf_bytes,
274
  file_name=f"{prefix} {selected_md}.pdf" if selected_md else f"{prefix} output.pdf",
275
  mime="application/pdf"
276
- )
 
20
 
21
  st.set_page_config(layout="wide", initial_sidebar_state="collapsed")
22
 
23
+ # πŸ•’ Time flies when you're having function calls
24
  def get_timestamp_prefix():
25
+ """πŸ•°οΈ Gets a timestamp that's more reliable than your coworker's ETA estimates"""
26
  central = pytz.timezone("US/Central")
27
  now = datetime.now(central)
28
  # Format: three-letter day, MMDD, HHMM + AM/PM (all uppercase)
29
  return now.strftime("%a %m%d %I%M%p").upper()
30
 
31
+ # 🧹 Because text needs a bath before being spoken
32
  def clean_for_speech(text):
33
+ """🧼 Scrubs your text cleaner than your bathroom will ever be"""
34
  # Remove hash marks
35
  text = text.replace("#", "")
36
  # Remove emojis using a regex pattern that covers a wide range
 
49
  text = emoji_pattern.sub('', text)
50
  return text
51
 
52
+ # 🎀 Making robots talk so you don't have to
53
  async def generate_audio(text, voice, filename):
54
+ """πŸ”Š Turn text into speech, because reading is so last century"""
55
  communicate = edge_tts.Communicate(text, voice)
56
  await communicate.save(filename)
57
  return filename
58
 
59
+ # πŸ”— Detecting links like a digital bloodhound
60
+ def detect_and_convert_links(text):
61
+ """πŸ•ΈοΈ Finds URLs in your text and turns them into actual clickable links"""
62
+ # Pattern to find URLs (http/https/ftp/www)
63
+ url_pattern = re.compile(
64
+ r'(https?://|www\.)[^\s\[\]()<>{}]+(\.[^\s\[\]()<>{}]+)+(/[^\s\[\]()<>{}]*)?',
65
+ re.IGNORECASE
66
+ )
67
+
68
+ # Pattern to find markdown links [text](url)
69
+ md_link_pattern = re.compile(r'\[(.*?)\]\((https?://[^\s\[\]()<>{}]+)\)')
70
+
71
+ # First convert markdown links
72
+ text = md_link_pattern.sub(r'<a href="\2">\1</a>', text)
73
+
74
+ # Then find and convert plain URLs not already in tags
75
+ start_idx = 0
76
+ result = []
77
+
78
+ while start_idx < len(text):
79
+ # Find the next URL
80
+ match = url_pattern.search(text, start_idx)
81
+ if not match:
82
+ # No more URLs, add the remaining text
83
+ result.append(text[start_idx:])
84
+ break
85
+
86
+ # Check if the URL is already inside an <a> tag
87
+ # This is a simplified check. A more robust approach might use an HTML parser
88
+ prev_text = text[start_idx:match.start()]
89
+ tag_balance = prev_text.count('<a') - prev_text.count('</a')
90
+
91
+ if tag_balance > 0:
92
+ # URL is likely inside a tag, skip this match
93
+ result.append(text[start_idx:match.end()])
94
+ else:
95
+ # Add text before the URL
96
+ result.append(text[start_idx:match.start()])
97
+
98
+ # Get the URL
99
+ url = match.group(0)
100
+
101
+ # Add proper URL prefix if needed
102
+ if url.startswith('www.'):
103
+ url_with_prefix = 'http://' + url
104
+ else:
105
+ url_with_prefix = url
106
+
107
+ # Add the URL as a link
108
+ result.append(f'<a href="{url_with_prefix}">{url}</a>')
109
+
110
+ start_idx = match.end()
111
+
112
+ return ''.join(result)
113
+
114
+ # 🎭 Making emojis wear the right font costume
115
  def apply_emoji_font(text, emoji_font):
116
+ """πŸ¦„ Because emojis deserve their own font fashion show"""
117
+ # First handle links - temporarily replace them with placeholders
118
+ link_pattern = re.compile(r'<a\s+href="([^"]+)">(.*?)</a>')
119
+ links = []
120
+
121
+ def save_link(match):
122
+ link_idx = len(links)
123
+ links.append((match.group(1), match.group(2)))
124
+ return f"###LINK_{link_idx}###"
125
+
126
+ text = link_pattern.sub(save_link, text)
127
+
128
+ # Now handle bold formatting
129
+ text = re.sub(r'<b>(.*?)</b>', lambda m: f'###BOLD_START###{m.group(1)}###BOLD_END###', text)
130
+
131
+ # Apply emoji font replacement
132
  emoji_pattern = re.compile(
133
  r"([\U0001F300-\U0001F5FF"
134
  r"\U0001F600-\U0001F64F"
 
143
  r"\u2600-\u26FF"
144
  r"\u2700-\u27BF]+)"
145
  )
146
+
147
  def replace_emoji(match):
148
  emoji = match.group(1)
149
  emoji = unicodedata.normalize('NFC', emoji)
150
  return f'<font face="{emoji_font}">{emoji}</font>'
151
+
152
  segments = []
153
  last_pos = 0
154
+
155
  for match in emoji_pattern.finditer(text):
156
  start, end = match.span()
157
  if last_pos < start:
158
  segments.append(f'<font face="DejaVuSans">{text[last_pos:start]}</font>')
159
  segments.append(replace_emoji(match))
160
  last_pos = end
161
+
162
  if last_pos < len(text):
163
  segments.append(f'<font face="DejaVuSans">{text[last_pos:]}</font>')
164
+
165
+ combined_text = ''.join(segments)
166
+
167
+ # Restore bold tags
168
+ combined_text = combined_text.replace('###BOLD_START###', '</font><b><font face="DejaVuSans">')
169
+ combined_text = combined_text.replace('###BOLD_END###', '</font></b><font face="DejaVuSans">')
170
+
171
+ # Restore links
172
+ for i, (url, label) in enumerate(links):
173
+ placeholder = f"###LINK_{i}###"
174
+ if placeholder in combined_text:
175
+ # If the link is within a font tag, we need to close and reopen it
176
+ parts = combined_text.split(placeholder)
177
+ if len(parts) == 2:
178
+ before, after = parts
179
+ # Check if we're inside a font tag
180
+ if before.rfind('<font') > before.rfind('</font>'):
181
+ # Close font tag before link, reopen after
182
+ link_html = f'</font><a href="{url}">{label}</a><font face="DejaVuSans">'
183
+ combined_text = before + link_html + after
184
+ else:
185
+ # Simple replacement
186
+ combined_text = before + f'<a href="{url}">{label}</a>' + after
187
+
188
+ return combined_text
189
 
190
+ # πŸ“ Converting markdown to PDF content, because PDFs never go out of style
191
  def markdown_to_pdf_content(markdown_text, render_with_bold, auto_bold_numbers):
192
+ """πŸ“‹ Transforms your disorganized thoughts into structured PDF content"""
193
  lines = markdown_text.strip().split('\n')
194
  pdf_content = []
195
  number_pattern = re.compile(r'^\d+\.\s')
196
+
197
  for line in lines:
198
  line = line.strip()
199
  if not line or line.startswith('# '):
200
  continue
201
+
202
+ # Process links before any other formatting
203
+ line = detect_and_convert_links(line)
204
+
205
+ # Handle bold formatting
206
  if render_with_bold:
207
  line = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', line)
208
+
209
  if auto_bold_numbers and number_pattern.match(line):
210
+ # Only apply bold if not already entirely bold
211
  if not (line.startswith("<b>") and line.endswith("</b>")):
212
+ # If there's already some bold formatting inside, we need to handle carefully
213
+ if "<b>" in line and "</b>" in line:
214
+ # Complex case - for simplicity, just make the whole line bold
215
+ # but remove any existing bold tags first
216
+ line = re.sub(r'</?b>', '', line)
217
+ line = f"<b>{line}</b>"
218
+ else:
219
+ line = f"<b>{line}</b>"
220
+
221
  pdf_content.append(line)
222
+
223
  total_lines = len(pdf_content)
224
  return pdf_content, total_lines
225
 
226
+ # πŸ—οΈ Building PDFs like it's your second job
227
  def create_pdf(markdown_text, base_font_size, render_with_bold, auto_bold_numbers, enlarge_numbered, num_columns):
228
+ """πŸ”¨ Constructs a PDF with the precision of a sleep-deprived architect"""
229
  buffer = io.BytesIO()
230
  page_width = A4[0] * 2
231
  page_height = A4[1]
 
233
  styles = getSampleStyleSheet()
234
  spacer_height = 10
235
  pdf_content, total_lines = markdown_to_pdf_content(markdown_text, render_with_bold, auto_bold_numbers)
236
+
237
+ # Define styles for different text types
238
  item_style = ParagraphStyle(
239
  'ItemStyle', parent=styles['Normal'], fontName="DejaVuSans",
240
+ fontSize=base_font_size, leading=base_font_size * 1.15, spaceAfter=1,
241
+ linkUnderline=True # Enable underline for links
242
  )
243
  numbered_bold_style = ParagraphStyle(
244
  'NumberedBoldStyle', parent=styles['Normal'], fontName="NotoEmoji-Bold",
245
  fontSize=base_font_size + 1 if enlarge_numbered else base_font_size,
246
+ leading=(base_font_size + 1) * 1.15 if enlarge_numbered else base_font_size * 1.15, spaceAfter=1,
247
+ linkUnderline=True # Enable underline for links
248
  )
249
  section_style = ParagraphStyle(
250
  'SectionStyle', parent=styles['Heading2'], fontName="DejaVuSans",
251
+ textColor=colors.darkblue, fontSize=base_font_size * 1.1, leading=base_font_size * 1.32, spaceAfter=2,
252
+ linkUnderline=True # Enable underline for links
253
  )
254
+
255
+ # Register fonts
256
  try:
257
  available_font_files = glob.glob("*.ttf")
258
  if not available_font_files:
 
269
  except Exception as e:
270
  st.error(f"Font registration error: {e}")
271
  return
272
+
273
+ # Distribute content across columns
274
  columns = [[] for _ in range(num_columns)]
275
  lines_per_column = total_lines / num_columns if num_columns > 0 else total_lines
276
  current_line_count = 0
277
  current_column = 0
278
  number_pattern = re.compile(r'^\d+\.\s')
279
+
280
  for item in pdf_content:
281
  if current_line_count >= lines_per_column and current_column < num_columns - 1:
282
  current_column += 1
283
  current_line_count = 0
284
  columns[current_column].append(item)
285
  current_line_count += 1
286
+
287
+ # Format columns into Paragraph objects
288
  column_cells = [[] for _ in range(num_columns)]
289
  for col_idx, column in enumerate(columns):
290
  for item in column:
 
296
  column_cells[col_idx].append(Paragraph(apply_emoji_font(content, "NotoEmoji-Bold"), section_style))
297
  else:
298
  column_cells[col_idx].append(Paragraph(apply_emoji_font(item, "DejaVuSans"), item_style))
299
+
300
+ # Ensure columns have the same number of cells
301
  max_cells = max(len(cells) for cells in column_cells) if column_cells else 0
302
  for cells in column_cells:
303
  cells.extend([Paragraph("", item_style)] * (max_cells - len(cells)))
304
+
305
+ # Create the table layout
306
  col_width = (page_width - 72) / num_columns if num_columns > 0 else page_width - 72
307
  table_data = list(zip(*column_cells)) if column_cells else [[]]
308
  table = Table(table_data, colWidths=[col_width] * num_columns, hAlign='CENTER')
 
317
  ('TOPPADDING', (0, 0), (-1, -1), 1),
318
  ('BOTTOMPADDING', (0, 0), (-1, -1), 1),
319
  ]))
320
+
321
+ # Build the PDF
322
  story = [Spacer(1, spacer_height), table]
323
  doc.build(story)
324
  buffer.seek(0)
325
  return buffer.getvalue()
326
 
327
+ # πŸ–ΌοΈ Converting PDFs to images, because we can't leave well enough alone
328
  def pdf_to_image(pdf_bytes):
329
+ """πŸ”Ž Turns your PDF into pictures because some people just want to see the world rendered"""
330
  try:
331
  doc = fitz.open(stream=pdf_bytes, filetype="pdf")
332
  images = []
 
344
  md_files = [f for f in glob.glob("*.md") if os.path.basename(f) != "README.md"]
345
  md_options = [os.path.splitext(os.path.basename(f))[0] for f in md_files]
346
 
347
+ # πŸŽͺ The main Streamlit show begins here
348
  with st.sidebar:
349
  st.markdown("### PDF Options")
350
  if md_options:
 
404
  mime="audio/mpeg"
405
  )
406
 
407
+ # πŸš€ Generating the PDF with more complexity than a rocket launch
408
  with st.spinner("Generating PDF..."):
409
  pdf_bytes = create_pdf(st.session_state.markdown_content, base_font_size, render_with_bold, auto_bold_numbers, enlarge_numbered, num_columns)
410
 
411
+ # πŸ“Ί Displaying the preview, because everyone loves to window shop
412
  with st.container():
413
  pdf_images = pdf_to_image(pdf_bytes)
414
  if pdf_images:
 
417
  else:
418
  st.info("Download the PDF to view it locally.")
419
 
420
+ # πŸ’Ύ Last chance to save your masterpiece before it's gone forever
421
  with st.sidebar:
422
  st.download_button(
423
  label="πŸ’ΎπŸ“„ Save PDF",
424
  data=pdf_bytes,
425
  file_name=f"{prefix} {selected_md}.pdf" if selected_md else f"{prefix} output.pdf",
426
  mime="application/pdf"
427
+ )