awacke1 commited on
Commit
7eca515
·
verified ·
1 Parent(s): 13ee1b3

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +282 -0
app.py ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import os
3
+ import re
4
+ import glob
5
+ import textwrap
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+
9
+ import streamlit as st
10
+ import pandas as pd
11
+ from PIL import Image
12
+ from reportlab.pdfgen import canvas
13
+ from reportlab.lib.pagesizes import letter
14
+ from reportlab.lib.utils import ImageReader
15
+ import mistune
16
+ from gtts import gTTS
17
+
18
+ # Page config
19
+ st.set_page_config(page_title="PDF & Code Interpreter", layout="wide", page_icon="🚀")
20
+
21
+ def delete_asset(path):
22
+ try:
23
+ os.remove(path)
24
+ except Exception as e:
25
+ st.error(f"Error deleting file: {e}")
26
+ st.rerun()
27
+
28
+ # Tabs setup
29
+ tab1, tab2 = st.tabs(["📄 PDF Composer", "🧪 Code Interpreter"])
30
+
31
+ with tab1:
32
+ st.header("📄 PDF Composer & Voice Generator 🚀")
33
+
34
+ # Sidebar PDF text settings
35
+ columns = st.sidebar.slider("Text columns", 1, 3, 1)
36
+ font_family = st.sidebar.selectbox("Font", ["Helvetica","Times-Roman","Courier"])
37
+ font_size = st.sidebar.slider("Font size", 6, 24, 12)
38
+
39
+ # Markdown input
40
+ md_file = st.file_uploader("Upload Markdown (.md)", type=["md"])
41
+ if md_file:
42
+ md_text = md_file.getvalue().decode("utf-8")
43
+ stem = Path(md_file.name).stem
44
+ else:
45
+ md_text = st.text_area("Or enter markdown text directly", height=200)
46
+ stem = datetime.now().strftime('%Y%m%d_%H%M%S')
47
+
48
+ # Convert Markdown to plain text
49
+ # Using mistune to parse markdown to HTML, then stripping HTML tags
50
+ renderer = mistune.HTMLRenderer()
51
+ markdown = mistune.create_markdown(renderer=renderer)
52
+ html = markdown(md_text or "")
53
+ plain_text = re.sub(r'<[^>]+>', '', html) # Strip HTML tags
54
+
55
+ # Voice settings
56
+ languages = {"English (US)": "en", "English (UK)": "en-uk", "Spanish": "es"}
57
+ voice_choice = st.selectbox("Voice Language", list(languages.keys()))
58
+ voice_lang = languages[voice_choice]
59
+ slow = st.checkbox("Slow Speech")
60
+
61
+ if st.button("🔊 Generate & Download Voice MP3 from Text"):
62
+ if plain_text.strip():
63
+ voice_file = f"{stem}.mp3"
64
+ try:
65
+ tts = gTTS(text=plain_text, lang=voice_lang, slow=slow)
66
+ tts.save(voice_file)
67
+ st.audio(voice_file)
68
+ with open(voice_file, 'rb') as mp3:
69
+ st.download_button("📥 Download MP3", data=mp3, file_name=voice_file, mime="audio/mpeg")
70
+ except Exception as e:
71
+ st.error(f"Error generating voice: {e}")
72
+ else:
73
+ st.warning("No text to generate voice from.")
74
+
75
+ # Image uploads and ordering
76
+ imgs = st.file_uploader("Upload Images for PDF", type=["png", "jpg", "jpeg"], accept_multiple_files=True)
77
+ ordered_images = []
78
+ if imgs:
79
+ # Create a DataFrame for editing image order
80
+ df_imgs = pd.DataFrame([{"name": f.name, "order": i} for i, f in enumerate(imgs)])
81
+ edited = st.data_editor(df_imgs, use_container_width=True, num_rows="dynamic") # Use num_rows="dynamic" for better UI
82
+ # Reconstruct the ordered list of file objects
83
+ for _, row in edited.sort_values("order").iterrows():
84
+ for f in imgs:
85
+ if f.name == row['name']:
86
+ ordered_images.append(f)
87
+ break # Found the file object, move to the next row
88
+
89
+ if st.button("🖋️ Generate PDF with Markdown & Images"):
90
+ if not plain_text.strip() and not ordered_images:
91
+ st.warning("Please provide some text or upload images to generate a PDF.")
92
+ else:
93
+ buf = io.BytesIO()
94
+ c = canvas.Canvas(buf)
95
+
96
+ # Render text with columns if there is text
97
+ if plain_text.strip():
98
+ page_w, page_h = letter
99
+ margin = 40
100
+ gutter = 20
101
+ col_w = (page_w - 2*margin - (columns-1)*gutter) / columns
102
+ c.setFont(font_family, font_size)
103
+ line_height = font_size * 1.2
104
+ col = 0
105
+ x = margin
106
+ y = page_h - margin
107
+ # Estimate wrap width based on font size and column width
108
+ # This is an approximation and might need fine-tuning
109
+ avg_char_width = font_size * 0.6 # Approximation
110
+ wrap_width = int(col_w / avg_char_width) if avg_char_width > 0 else 100 # Prevent division by zero
111
+
112
+ for paragraph in plain_text.split("\n"):
113
+ # Handle empty lines by adding vertical space
114
+ if not paragraph.strip():
115
+ y -= line_height
116
+ if y < margin:
117
+ col += 1
118
+ if col >= columns:
119
+ c.showPage()
120
+ c.setFont(font_family, font_size)
121
+ col = 0
122
+ x = margin + col*(col_w+gutter)
123
+ y = page_h - margin
124
+ continue # Skip to next paragraph
125
+
126
+ for line in textwrap.wrap(paragraph, wrap_width):
127
+ if y < margin:
128
+ col += 1
129
+ if col >= columns:
130
+ c.showPage()
131
+ c.setFont(font_family, font_size)
132
+ col = 0
133
+ x = margin + col*(col_w+gutter)
134
+ y = page_h - margin
135
+ c.drawString(x, y, line)
136
+ y -= line_height
137
+ y -= line_height # Add space between paragraphs
138
+
139
+ # Autosize pages to each image
140
+ for img_f in ordered_images:
141
+ try:
142
+ img = Image.open(img_f)
143
+ w, h = img.size
144
+ c.showPage() # Start a new page for each image
145
+ # Set page size to image size
146
+ c.setPageSize((w, h))
147
+ # Draw image filling the page
148
+ c.drawImage(ImageReader(img), 0, 0, w, h, preserveAspectRatio=False) # Use False to fill page exactly
149
+ except Exception as e:
150
+ st.warning(f"Could not process image {img_f.name}: {e}")
151
+ continue
152
+
153
+ c.save()
154
+ buf.seek(0)
155
+ pdf_name = f"{stem}.pdf"
156
+ st.download_button("⬇️ Download PDF", data=buf, file_name=pdf_name, mime="application/pdf")
157
+
158
+ st.markdown("---")
159
+ st.subheader("📂 Available Assets")
160
+ # Get all files and filter out unwanted ones
161
+ all_assets = glob.glob("*.*")
162
+ excluded_extensions = ['.py', '.ttf']
163
+ excluded_files = ['README.md', 'index.html']
164
+
165
+ assets = sorted([
166
+ a for a in all_assets
167
+ if not (a.lower().endswith(tuple(excluded_extensions)) or a in excluded_files)
168
+ ])
169
+
170
+ if not assets:
171
+ st.info("No available assets found.")
172
+ else:
173
+ for a in assets:
174
+ ext = a.split('.')[-1].lower()
175
+ cols = st.columns([3, 1, 1])
176
+ cols[0].write(a)
177
+
178
+ # Provide download/preview based on file type
179
+ try:
180
+ if ext == 'pdf':
181
+ with open(a, 'rb') as fp:
182
+ cols[1].download_button("📥", data=fp, file_name=a, mime="application/pdf")
183
+ elif ext == 'mp3':
184
+ # Streamlit can play audio directly from file path
185
+ cols[1].audio(a)
186
+ with open(a, 'rb') as mp3:
187
+ cols[1].download_button("📥", data=mp3, file_name=a, mime="audio/mpeg")
188
+ # Add more file types here if needed (e.g., images)
189
+ elif ext in ['png', 'jpg', 'jpeg', 'gif']:
190
+ # Can't preview image directly in this column, offer download
191
+ with open(a, 'rb') as img_file:
192
+ cols[1].download_button("⬇️", data=img_file, file_name=a, mime=f"image/{ext}")
193
+ # Handle other file types - maybe just offer download
194
+ else:
195
+ with open(a, 'rb') as other_file:
196
+ cols[1].download_button("⬇️", data=other_file, file_name=a) # Mime type is guessed by streamlit
197
+
198
+ # Delete button
199
+ cols[2].button("🗑️", key=f"del_{a}", on_click=delete_asset, args=(a,))
200
+ except Exception as e:
201
+ cols[2].error(f"Error handling file {a}: {e}")
202
+
203
+
204
+ with tab2:
205
+ st.header("🧪 Python Code Executor & Demo")
206
+ import io, sys
207
+ from contextlib import redirect_stdout
208
+
209
+ DEFAULT_CODE = '''import streamlit as st
210
+ import random
211
+
212
+ st.title("📊 Demo App")
213
+ st.markdown("Random number and color demo")
214
+
215
+ col1, col2 = st.columns(2)
216
+ with col1:
217
+ num = st.number_input("Number:", 1, 100, 10)
218
+ mul = st.slider("Multiplier:", 1, 10, 2)
219
+ if st.button("Calc"):
220
+ st.write(num * mul)
221
+ with col2:
222
+ color = st.color_picker("Pick color","#ff0000")
223
+ st.markdown(f'<div style="background:{color};padding:10px;">Color</div>', unsafe_allow_html=True)
224
+ ''' # noqa
225
+
226
+ def extract_python_code(md: str) -> list:
227
+ # Find all blocks starting with ```python and ending with ```
228
+ return re.findall(r"```python\s*(.*?)```", md, re.DOTALL)
229
+
230
+ def execute_code(code: str) -> tuple:
231
+ buf = io.StringIO(); local_vars = {}
232
+ # Redirect stdout to capture print statements
233
+ try:
234
+ with redirect_stdout(buf):
235
+ # Use exec to run the code. locals() and globals() are needed.
236
+ # Passing empty dicts might limit some functionalities but provides isolation.
237
+ exec(code, {}, local_vars)
238
+ return buf.getvalue(), None # Return captured output
239
+ except Exception as e:
240
+ return None, str(e) # Return error message
241
+
242
+ up = st.file_uploader("Upload .py or .md", type=['py', 'md'])
243
+ # Initialize session state for code if it doesn't exist
244
+ if 'code' not in st.session_state:
245
+ st.session_state.code = DEFAULT_CODE
246
+
247
+ if up:
248
+ text = up.getvalue().decode()
249
+ if up.type == 'text/markdown':
250
+ codes = extract_python_code(text)
251
+ if codes:
252
+ # Take the first python code block found
253
+ st.session_state.code = codes[0].strip()
254
+ else:
255
+ st.warning("No Python code block found in the markdown file.")
256
+ st.session_state.code = '' # Clear code if no block found
257
+ else: # .py file
258
+ st.session_state.code = text.strip()
259
+
260
+ # Display the code after upload
261
+ st.code(st.session_state.code, language='python')
262
+ else:
263
+ # Text area for code editing if no file is uploaded or after processing upload
264
+ st.session_state.code = st.text_area("💻 Code Editor", value=st.session_state.code, height=400) # Increased height
265
+
266
+ c1, c2 = st.columns([1, 1])
267
+ if c1.button("▶️ Run Code"):
268
+ if st.session_state.code.strip():
269
+ out, err = execute_code(st.session_state.code)
270
+ if err:
271
+ st.error(f"Execution Error:\n{err}")
272
+ elif out:
273
+ st.subheader("Output:")
274
+ st.code(out)
275
+ else:
276
+ st.success("Executed with no standard output.")
277
+ else:
278
+ st.warning("No code to run.")
279
+
280
+ if c2.button("🗑️ Clear Code"):
281
+ st.session_state.code = ''
282
+ st.rerun()