|
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 |
|
|
|
|
|
st.set_page_config(page_title="PDF & Code Interpreter", layout="wide", page_icon="π") |
|
|
|
def delete_asset(path): |
|
try: |
|
os.remove(path) |
|
except: |
|
pass |
|
st.rerun() |
|
|
|
|
|
tab1, tab2 = st.tabs(["π PDF Composer", "π§ͺ Code Interpreter"]) |
|
|
|
with tab1: |
|
st.header("π PDF Composer & Voice Generator π") |
|
|
|
|
|
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) |
|
|
|
|
|
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') |
|
|
|
|
|
renderer = mistune.HTMLRenderer() |
|
markdown = mistune.create_markdown(renderer=renderer) |
|
html = markdown(md_text or "") |
|
plain_text = re.sub(r'<[^>]+>', '', html) |
|
|
|
|
|
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" |
|
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") |
|
else: |
|
st.warning("No text to generate voice from.") |
|
|
|
|
|
imgs = st.file_uploader("Upload Images for PDF", type=["png", "jpg", "jpeg"], accept_multiple_files=True) |
|
ordered_images = [] |
|
if imgs: |
|
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) |
|
for _, row in edited.sort_values("order").iterrows(): |
|
for f in imgs: |
|
if f.name == row['name']: |
|
ordered_images.append(f) |
|
break |
|
|
|
if st.button("ποΈ Generate PDF with Markdown & Images"): |
|
buf = io.BytesIO() |
|
c = canvas.Canvas(buf) |
|
|
|
|
|
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 |
|
wrap_width = int(col_w / (font_size * 0.6)) |
|
|
|
for paragraph in plain_text.split("\n"): |
|
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 |
|
|
|
|
|
for img_f in ordered_images: |
|
try: |
|
img = Image.open(img_f) |
|
w, h = img.size |
|
c.showPage() |
|
c.setPageSize((w, h)) |
|
c.drawImage(ImageReader(img), 0, 0, w, h, preserveAspectRatio=True, mask='auto') |
|
except: |
|
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") |
|
assets = sorted(glob.glob("*.*")) |
|
for a in assets: |
|
ext = a.split('.')[-1].lower() |
|
cols = st.columns([3, 1, 1]) |
|
cols[0].write(a) |
|
if ext == 'pdf': |
|
with open(a, 'rb') as fp: |
|
cols[1].download_button("π₯", data=fp, file_name=a, mime="application/pdf") |
|
elif ext == 'mp3': |
|
cols[1].audio(a) |
|
with open(a, 'rb') as mp3: |
|
cols[1].download_button("π₯", data=mp3, file_name=a, mime="audio/mpeg") |
|
cols[2].button("ποΈ", key=f"del_{a}", on_click=delete_asset, args=(a,)) |
|
|
|
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'<div style="background:{color};padding:10px;">Color</div>', unsafe_allow_html=True) |
|
''' |
|
|
|
def extract_python_code(md: str) -> list: |
|
return re.findall(r"```python\s*(.*?)```", md, re.DOTALL) |
|
|
|
def execute_code(code: str) -> tuple: |
|
buf = io.StringIO(); local_vars = {} |
|
try: |
|
with redirect_stdout(buf): exec(code, {}, local_vars) |
|
return buf.getvalue(), None |
|
except Exception as e: |
|
return None, str(e) |
|
|
|
up = st.file_uploader("Upload .py or .md", type=['py', 'md']) |
|
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) |
|
st.session_state.code = codes[0] if codes else '' |
|
else: |
|
st.session_state.code = text |
|
st.code(st.session_state.code, language='python') |
|
else: |
|
st.session_state.code = st.text_area("π» Code Editor", value=st.session_state.code, height=300) |
|
|
|
c1, c2 = st.columns([1, 1]) |
|
if c1.button("βΆοΈ Run Code"): |
|
out, err = execute_code(st.session_state.code) |
|
if err: |
|
st.error(err) |
|
elif out: |
|
st.code(out) |
|
else: |
|
st.success("Executed with no output.") |
|
if c2.button("ποΈ Clear Code"): |
|
st.session_state.code = '' |
|
st.rerun() |
|
|