|
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 |
|
|
|
|
|
|
|
|
|
def delete_asset(path): |
|
"""Safely deletes a file if it exists and reruns the Streamlit app.""" |
|
try: |
|
os.remove(path) |
|
except OSError as e: |
|
st.error(f"Error deleting file {path}: {e}") |
|
st.rerun() |
|
|
|
|
|
def get_text_input(file_uploader_label, accepted_types, text_area_label): |
|
""" |
|
Provides UI for uploading a text file or entering text manually. |
|
Returns the text content and a filename stem. |
|
""" |
|
md_text = "" |
|
stem = datetime.now().strftime('%Y%m%d_%H%M%S') |
|
|
|
uploaded_file = st.file_uploader(file_uploader_label, type=accepted_types) |
|
if uploaded_file: |
|
md_text = uploaded_file.getvalue().decode("utf-8") |
|
stem = Path(uploaded_file.name).stem |
|
else: |
|
md_text = st.text_area(text_area_label, height=200) |
|
|
|
|
|
renderer = mistune.HTMLRenderer() |
|
markdown = mistune.create_markdown(renderer=renderer) |
|
html = markdown(md_text or "") |
|
plain_text = re.sub(r'<[^>]+>', '', html) |
|
|
|
return plain_text, stem |
|
|
|
|
|
def generate_voice_file(text, lang, is_slow, filename): |
|
""" |
|
Creates an MP3 from text, saves it, and provides it for playback and download. |
|
""" |
|
if not text.strip(): |
|
st.warning("No text to generate voice from.") |
|
return |
|
|
|
voice_file_path = f"{filename}.mp3" |
|
try: |
|
tts = gTTS(text=text, lang=lang, slow=is_slow) |
|
tts.save(voice_file_path) |
|
st.audio(voice_file_path) |
|
with open(voice_file_path, 'rb') as fp: |
|
st.download_button("π₯ Download MP3", data=fp, file_name=voice_file_path, mime="audio/mpeg") |
|
except Exception as e: |
|
st.error(f"Failed to generate audio: {e}") |
|
|
|
|
|
def generate_pdf(text_content, images, pdf_params): |
|
""" |
|
Generates a PDF buffer from text and a list of images based on specified parameters. |
|
""" |
|
buf = io.BytesIO() |
|
c = canvas.Canvas(buf) |
|
page_w, page_h = letter |
|
margin = 40 |
|
gutter = 20 |
|
col_w = (page_w - 2 * margin - (pdf_params['columns'] - 1) * gutter) / pdf_params['columns'] |
|
|
|
c.setFont(pdf_params['font_family'], pdf_params['font_size']) |
|
line_height = pdf_params['font_size'] * 1.2 |
|
wrap_width = int(col_w / (pdf_params['font_size'] * 0.6)) |
|
|
|
y = page_h - margin |
|
col_idx = 0 |
|
|
|
|
|
for paragraph in text_content.split("\n"): |
|
wrapped_lines = textwrap.wrap(paragraph, wrap_width) if paragraph.strip() else [""] |
|
for line in wrapped_lines: |
|
if y < margin: |
|
col_idx += 1 |
|
if col_idx >= pdf_params['columns']: |
|
c.showPage() |
|
c.setFont(pdf_params['font_family'], pdf_params['font_size']) |
|
col_idx = 0 |
|
y = page_h - margin |
|
|
|
x = margin + col_idx * (col_w + gutter) |
|
c.drawString(x, y, line) |
|
y -= line_height |
|
y -= line_height |
|
|
|
|
|
for img_file in images: |
|
try: |
|
img = Image.open(img_file) |
|
w, h = img.size |
|
c.showPage() |
|
c.setPageSize((w, h)) |
|
c.drawImage(ImageReader(img), 0, 0, w, h, preserveAspectRatio=True, mask='auto') |
|
except Exception as e: |
|
st.warning(f"Could not process image {img_file.name}: {e}") |
|
continue |
|
|
|
c.save() |
|
buf.seek(0) |
|
return buf |
|
|
|
|
|
def show_asset_manager(): |
|
"""Scans for local files and displays them with management controls.""" |
|
st.markdown("---") |
|
st.subheader("π Available Assets") |
|
assets = sorted(glob.glob("*.*")) |
|
if not assets: |
|
st.info("No assets generated yet.") |
|
return |
|
|
|
for asset_path in assets: |
|
ext = asset_path.split('.')[-1].lower() |
|
cols = st.columns([3, 1, 1]) |
|
cols[0].write(asset_path) |
|
|
|
try: |
|
with open(asset_path, 'rb') as fp: |
|
file_bytes = fp.read() |
|
|
|
if ext == 'pdf': |
|
cols[1].download_button("π₯", data=file_bytes, file_name=asset_path, mime="application/pdf", key=f"dl_{asset_path}") |
|
elif ext == 'mp3': |
|
cols[1].audio(file_bytes) |
|
except Exception as e: |
|
cols[1].error("Error reading file.") |
|
|
|
cols[2].button("ποΈ", key=f"del_{asset_path}", on_click=delete_asset, args=(asset_path,)) |
|
|
|
|
|
def render_code_interpreter(): |
|
"""Sets up the UI and execution logic for the code interpreter tab.""" |
|
st.header("π§ͺ Python Code Executor & Demo") |
|
|
|
|
|
def extract_python_code(md_text): |
|
return re.findall(r"```python\s*(.*?)```", md_text, re.DOTALL) |
|
|
|
def execute_code(code_str): |
|
output_buffer = io.StringIO() |
|
try: |
|
with redirect_stdout(output_buffer): |
|
exec(code_str, {}) |
|
return output_buffer.getvalue(), None |
|
except Exception as e: |
|
return None, str(e) |
|
|
|
|
|
DEFAULT_CODE = "import streamlit as st\n\nst.balloons()\nst.write('Hello, World!')" |
|
if 'code' not in st.session_state: |
|
st.session_state.code = DEFAULT_CODE |
|
|
|
uploaded_file = st.file_uploader("Upload .py or .md", type=['py', 'md']) |
|
if uploaded_file: |
|
file_content = uploaded_file.getvalue().decode() |
|
if uploaded_file.type == 'text/markdown': |
|
codes = extract_python_code(file_content) |
|
st.session_state.code = codes[0] if codes else '' |
|
else: |
|
st.session_state.code = file_content |
|
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(2) |
|
if c1.button("βΆοΈ Run Code", use_container_width=True): |
|
output, err = execute_code(st.session_state.code) |
|
if err: |
|
st.error(err) |
|
elif output: |
|
st.code(output, language='text') |
|
else: |
|
st.success("Executed successfully with no output.") |
|
if c2.button("ποΈ Clear Code", use_container_width=True): |
|
st.session_state.code = '' |
|
st.rerun() |
|
|
|
|
|
def main(): |
|
"""Main function to run the Streamlit application.""" |
|
st.set_page_config(page_title="PDF & Code Interpreter", layout="wide", page_icon="π") |
|
|
|
tab1, tab2 = st.tabs(["π PDF Composer", "π§ͺ Code Interpreter"]) |
|
|
|
with tab1: |
|
st.header("π PDF Composer & Voice Generator π") |
|
|
|
|
|
st.sidebar.title("PDF Settings") |
|
pdf_params = { |
|
'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), |
|
} |
|
|
|
|
|
plain_text, filename_stem = get_text_input( |
|
"Upload Markdown (.md)", ["md"], "Or enter markdown text directly" |
|
) |
|
|
|
st.subheader("π£οΈ Voice Generation") |
|
languages = {"English (US)": "en", "English (UK)": "en-uk", "Spanish": "es"} |
|
voice_choice = st.selectbox("Voice Language", list(languages.keys())) |
|
slow_speech = st.checkbox("Slow Speech") |
|
|
|
if st.button("π Generate Voice MP3"): |
|
generate_voice_file(plain_text, languages[voice_choice], slow_speech, filename_stem) |
|
|
|
st.subheader("πΌοΈ Image Upload") |
|
uploaded_images = st.file_uploader( |
|
"Upload Images for PDF", type=["png", "jpg", "jpeg"], accept_multiple_files=True |
|
) |
|
|
|
ordered_images = [] |
|
if uploaded_images: |
|
df_imgs = pd.DataFrame([{"name": f.name, "order": i} for i, f in enumerate(uploaded_images)]) |
|
edited_df = st.data_editor(df_imgs, use_container_width=True, key="img_order_editor") |
|
|
|
|
|
image_map = {f.name: f for f in uploaded_images} |
|
|
|
|
|
for _, row in edited_df.sort_values("order").iterrows(): |
|
if row['name'] in image_map: |
|
ordered_images.append(image_map[row['name']]) |
|
|
|
st.subheader("ποΈ PDF Generation") |
|
if st.button("Generate PDF with Markdown & Images"): |
|
pdf_buffer = generate_pdf(plain_text, ordered_images, pdf_params) |
|
st.download_button( |
|
"β¬οΈ Download PDF", |
|
data=pdf_buffer, |
|
file_name=f"{filename_stem}.pdf", |
|
mime="application/pdf" |
|
) |
|
|
|
show_asset_manager() |
|
|
|
with tab2: |
|
render_code_interpreter() |
|
|
|
if __name__ == "__main__": |
|
main() |