import io import os import re import glob import textwrap from datetime import datetime from pathlib import Path import streamlit as st import pandas as pd from PIL import Image from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import letter from reportlab.lib.utils import ImageReader import mistune from gtts import gTTS # Page config st.set_page_config(page_title="PDF & Code Interpreter", layout="wide", page_icon="๐Ÿš€") def delete_asset(path): try: os.remove(path) except Exception as e: st.error(f"Error deleting file: {e}") st.rerun() # Tabs setup tab1, tab2 = st.tabs(["๐Ÿ“„ PDF Composer", "๐Ÿงช Code Interpreter"]) with tab1: st.header("๐Ÿ“„ PDF Composer & Voice Generator ๐Ÿš€") # Sidebar PDF text settings columns = st.sidebar.slider("Text columns", 1, 3, 1) font_family = st.sidebar.selectbox("Font", ["Helvetica","Times-Roman","Courier"]) font_size = st.sidebar.slider("Font size", 6, 24, 12) # Markdown input md_file = st.file_uploader("Upload Markdown (.md)", type=["md"]) if md_file: md_text = md_file.getvalue().decode("utf-8") stem = Path(md_file.name).stem else: md_text = st.text_area("Or enter markdown text directly", height=200) stem = datetime.now().strftime('%Y%m%d_%H%M%S') # Convert Markdown to plain text # Using mistune to parse markdown to HTML, then stripping HTML tags renderer = mistune.HTMLRenderer() markdown = mistune.create_markdown(renderer=renderer) html = markdown(md_text or "") plain_text = re.sub(r'<[^>]+>', '', html) # Strip HTML tags # Voice settings languages = {"English (US)": "en", "English (UK)": "en-uk", "Spanish": "es"} voice_choice = st.selectbox("Voice Language", list(languages.keys())) voice_lang = languages[voice_choice] slow = st.checkbox("Slow Speech") if st.button("๐Ÿ”Š Generate & Download Voice MP3 from Text"): if plain_text.strip(): voice_file = f"{stem}.mp3" try: tts = gTTS(text=plain_text, lang=voice_lang, slow=slow) tts.save(voice_file) st.audio(voice_file) with open(voice_file, 'rb') as mp3: st.download_button("๐Ÿ“ฅ Download MP3", data=mp3, file_name=voice_file, mime="audio/mpeg") except Exception as e: st.error(f"Error generating voice: {e}") else: st.warning("No text to generate voice from.") # Image uploads and ordering imgs = st.file_uploader("Upload Images for PDF", type=["png", "jpg", "jpeg"], accept_multiple_files=True) ordered_images = [] if imgs: # Create a DataFrame for editing image order df_imgs = pd.DataFrame([{"name": f.name, "order": i} for i, f in enumerate(imgs)]) edited = st.data_editor(df_imgs, use_container_width=True, num_rows="dynamic") # Use num_rows="dynamic" for better UI # Reconstruct the ordered list of file objects for _, row in edited.sort_values("order").iterrows(): for f in imgs: if f.name == row['name']: ordered_images.append(f) break # Found the file object, move to the next row if st.button("๐Ÿ–‹๏ธ Generate PDF with Markdown & Images"): if not plain_text.strip() and not ordered_images: st.warning("Please provide some text or upload images to generate a PDF.") else: buf = io.BytesIO() c = canvas.Canvas(buf) # Render text with columns if there is text if plain_text.strip(): page_w, page_h = letter margin = 40 gutter = 20 col_w = (page_w - 2*margin - (columns-1)*gutter) / columns c.setFont(font_family, font_size) line_height = font_size * 1.2 col = 0 x = margin y = page_h - margin # Estimate wrap width based on font size and column width # This is an approximation and might need fine-tuning avg_char_width = font_size * 0.6 # Approximation wrap_width = int(col_w / avg_char_width) if avg_char_width > 0 else 100 # Prevent division by zero for paragraph in plain_text.split("\n"): # Handle empty lines by adding vertical space if not paragraph.strip(): y -= line_height if y < margin: col += 1 if col >= columns: c.showPage() c.setFont(font_family, font_size) col = 0 x = margin + col*(col_w+gutter) y = page_h - margin continue # Skip to next paragraph for line in textwrap.wrap(paragraph, wrap_width): if y < margin: col += 1 if col >= columns: c.showPage() c.setFont(font_family, font_size) col = 0 x = margin + col*(col_w+gutter) y = page_h - margin c.drawString(x, y, line) y -= line_height y -= line_height # Add space between paragraphs # Autosize pages to each image for img_f in ordered_images: try: img = Image.open(img_f) w, h = img.size c.showPage() # Start a new page for each image # Set page size to image size c.setPageSize((w, h)) # Draw image filling the page c.drawImage(ImageReader(img), 0, 0, w, h, preserveAspectRatio=False) # Use False to fill page exactly except Exception as e: st.warning(f"Could not process image {img_f.name}: {e}") continue c.save() buf.seek(0) pdf_name = f"{stem}.pdf" st.download_button("โฌ‡๏ธ Download PDF", data=buf, file_name=pdf_name, mime="application/pdf") st.markdown("---") st.subheader("๐Ÿ“‚ Available Assets") # Get all files and filter out unwanted ones all_assets = glob.glob("*.*") excluded_extensions = ['.py', '.ttf'] excluded_files = ['README.md', 'index.html'] assets = sorted([ a for a in all_assets if not (a.lower().endswith(tuple(excluded_extensions)) or a in excluded_files) ]) if not assets: st.info("No available assets found.") else: for a in assets: ext = a.split('.')[-1].lower() cols = st.columns([3, 1, 1]) cols[0].write(a) # Provide download/preview based on file type try: if ext == 'pdf': with open(a, 'rb') as fp: cols[1].download_button("๐Ÿ“ฅ", data=fp, file_name=a, mime="application/pdf") elif ext == 'mp3': # Streamlit can play audio directly from file path cols[1].audio(a) with open(a, 'rb') as mp3: cols[1].download_button("๐Ÿ“ฅ", data=mp3, file_name=a, mime="audio/mpeg") # Add more file types here if needed (e.g., images) elif ext in ['png', 'jpg', 'jpeg', 'gif']: # Can't preview image directly in this column, offer download with open(a, 'rb') as img_file: cols[1].download_button("โฌ‡๏ธ", data=img_file, file_name=a, mime=f"image/{ext}") # Handle other file types - maybe just offer download else: with open(a, 'rb') as other_file: cols[1].download_button("โฌ‡๏ธ", data=other_file, file_name=a) # Mime type is guessed by streamlit # Delete button cols[2].button("๐Ÿ—‘๏ธ", key=f"del_{a}", on_click=delete_asset, args=(a,)) except Exception as e: cols[2].error(f"Error handling file {a}: {e}") with tab2: st.header("๐Ÿงช Python Code Executor & Demo") import io, sys from contextlib import redirect_stdout DEFAULT_CODE = '''import streamlit as st import random st.title("๐Ÿ“Š Demo App") st.markdown("Random number and color demo") col1, col2 = st.columns(2) with col1: num = st.number_input("Number:", 1, 100, 10) mul = st.slider("Multiplier:", 1, 10, 2) if st.button("Calc"): st.write(num * mul) with col2: color = st.color_picker("Pick color","#ff0000") st.markdown(f'
Color
', unsafe_allow_html=True) ''' # noqa def extract_python_code(md: str) -> list: # Find all blocks starting with ```python and ending with ``` return re.findall(r"```python\s*(.*?)```", md, re.DOTALL) def execute_code(code: str) -> tuple: buf = io.StringIO(); local_vars = {} # Redirect stdout to capture print statements try: with redirect_stdout(buf): # Use exec to run the code. locals() and globals() are needed. # Passing empty dicts might limit some functionalities but provides isolation. exec(code, {}, local_vars) return buf.getvalue(), None # Return captured output except Exception as e: return None, str(e) # Return error message up = st.file_uploader("Upload .py or .md", type=['py', 'md']) # Initialize session state for code if it doesn't exist if 'code' not in st.session_state: st.session_state.code = DEFAULT_CODE if up: text = up.getvalue().decode() if up.type == 'text/markdown': codes = extract_python_code(text) if codes: # Take the first python code block found st.session_state.code = codes[0].strip() else: st.warning("No Python code block found in the markdown file.") st.session_state.code = '' # Clear code if no block found else: # .py file st.session_state.code = text.strip() # Display the code after upload st.code(st.session_state.code, language='python') else: # Text area for code editing if no file is uploaded or after processing upload st.session_state.code = st.text_area("๐Ÿ’ป Code Editor", value=st.session_state.code, height=400) # Increased height c1, c2 = st.columns([1, 1]) if c1.button("โ–ถ๏ธ Run Code"): if st.session_state.code.strip(): out, err = execute_code(st.session_state.code) if err: st.error(f"Execution Error:\n{err}") elif out: st.subheader("Output:") st.code(out) else: st.success("Executed with no standard output.") else: st.warning("No code to run.") if c2.button("๐Ÿ—‘๏ธ Clear Code"): st.session_state.code = '' st.rerun()