awacke1 commited on
Commit
1d4b8d3
·
verified ·
1 Parent(s): b52c5b7

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +165 -0
app.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import os
3
+ import re
4
+ from datetime import datetime
5
+ from collections import Counter
6
+
7
+ import pandas as pd
8
+ import streamlit as st
9
+ from PIL import Image
10
+ from reportlab.pdfgen import canvas
11
+ from reportlab.lib.utils import ImageReader
12
+
13
+ # --- App Configuration ----------------------------------
14
+ st.set_page_config(
15
+ page_title="Image → PDF Comic Layout",
16
+ layout="wide",
17
+ initial_sidebar_state="expanded",
18
+ )
19
+
20
+ st.title("🖼️ Image → PDF • Full-Page & Custom Layout Generator")
21
+ st.markdown(
22
+ "Upload images, scan documents, filter by orientation, reorder visually, and generate a captioned PDF where each page matches its image's dimensions."
23
+ )
24
+
25
+ # --- Sidebar: Page Settings -----------------------------
26
+ st.sidebar.header("1️⃣ Page Aspect Ratio & Size")
27
+ ratio_map = {
28
+ "4:3 (Landscape)": (4, 3),
29
+ "16:9 (Landscape)": (16, 9),
30
+ "1:1 (Square)": (1, 1),
31
+ "2:3 (Portrait)": (2, 3),
32
+ "9:16 (Portrait)": (9, 16),
33
+ }
34
+ ratio_choice = st.sidebar.selectbox(
35
+ "Preset Ratio", list(ratio_map.keys()) + ["Custom…"]
36
+ )
37
+ if ratio_choice != "Custom…":
38
+ rw, rh = ratio_map[ratio_choice]
39
+ else:
40
+ rw = st.sidebar.number_input("Custom Width Ratio", min_value=1, value=4)
41
+ rh = st.sidebar.number_input("Custom Height Ratio", min_value=1, value=3)
42
+ BASE_WIDTH_PT = st.sidebar.slider(
43
+ "Base Page Width (pt)", min_value=400, max_value=1200, value=800, step=100
44
+ )
45
+ page_width = BASE_WIDTH_PT
46
+ page_height = int(BASE_WIDTH_PT * (rh / rw))
47
+ st.sidebar.markdown(f"**Preview page size:** {page_width}×{page_height} pt")
48
+
49
+ # --- Main: Scan, Upload, Filter & Reorder --------------
50
+ st.header("2️⃣ Document Scan & Image Upload")
51
+ # Document scan via camera
52
+ cam_img = st.camera_input("📸 Scan Document", key="doc_scan")
53
+ if cam_img:
54
+ now = datetime.now()
55
+ prefix = now.strftime("%Y-%m%d-%I%M%p") + "-" + now.strftime("%a").upper()
56
+ scan_name = f"{prefix}-scan.png"
57
+ with open(scan_name, "wb") as f:
58
+ f.write(cam_img.getvalue())
59
+ st.image(Image.open(scan_name), caption=scan_name, use_container_width=True)
60
+
61
+ # Image upload
62
+ uploaded = st.file_uploader(
63
+ "📂 Select PNG/JPG images", type=["png","jpg","jpeg"], accept_multiple_files=True
64
+ )
65
+
66
+ ordered_files = []
67
+ if uploaded:
68
+ records = []
69
+ for idx, f in enumerate(uploaded):
70
+ im = Image.open(f)
71
+ w, h = im.size
72
+ ar = round(w / h, 2)
73
+ orient = "Square" if 0.9 <= ar <= 1.1 else ("Landscape" if ar > 1.1 else "Portrait")
74
+ records.append({
75
+ "filename": f.name,
76
+ "width": w,
77
+ "height": h,
78
+ "aspect_ratio": ar,
79
+ "orientation": orient,
80
+ "order": idx,
81
+ })
82
+ df = pd.DataFrame(records)
83
+ dims = st.sidebar.multiselect(
84
+ "Include orientations:", options=["Landscape","Portrait","Square"],
85
+ default=["Landscape","Portrait","Square"]
86
+ )
87
+ df = df[df["orientation"].isin(dims)].reset_index(drop=True)
88
+
89
+ st.markdown("#### Image Metadata & Order")
90
+ st.dataframe(df.style.format({"aspect_ratio": "{:.2f}"}), use_container_width=True)
91
+ st.markdown("*Drag rows or edit the `order` column to set PDF page sequence.*")
92
+ try:
93
+ edited = st.experimental_data_editor(df, num_rows="fixed", use_container_width=True)
94
+ ordered_df = edited
95
+ except Exception:
96
+ edited = st.data_editor(
97
+ df,
98
+ column_config={
99
+ "order": st.column_config.NumberColumn(
100
+ "Order", min_value=0, max_value=len(df)-1
101
+ )
102
+ },
103
+ hide_index=True,
104
+ use_container_width=True,
105
+ )
106
+ ordered_df = edited.sort_values("order").reset_index(drop=True)
107
+
108
+ name2file = {f.name: f for f in uploaded}
109
+ ordered_files = [name2file[n] for n in ordered_df["filename"] if n in name2file]
110
+
111
+ # --- Utility: Clean stems -------------------------------
112
+ def clean_stem(fn: str) -> str:
113
+ stem = os.path.splitext(fn)[0]
114
+ return stem.replace("-", " ").replace("_", " ")
115
+
116
+ # --- PDF Creation: Image-sized + Captions --------------
117
+ def make_image_sized_pdf(images):
118
+ buffer = io.BytesIO()
119
+ c = canvas.Canvas(buffer)
120
+ for idx, f in enumerate(images, start=1):
121
+ im = Image.open(f)
122
+ iw, ih = im.size
123
+ cap_h = 20
124
+ page_w, page_h = iw, ih + cap_h
125
+ c.setPageSize((page_w, page_h))
126
+ c.drawImage(ImageReader(im), 0, cap_h, iw, ih, preserveAspectRatio=True, mask='auto')
127
+ caption = clean_stem(f.name)
128
+ c.setFont("Helvetica", 12)
129
+ c.drawCentredString(page_w/2, cap_h/2, caption)
130
+ c.setFont("Helvetica", 8)
131
+ c.drawRightString(page_w - 10, 10, str(idx))
132
+ c.showPage()
133
+ c.save()
134
+ buffer.seek(0)
135
+ return buffer.getvalue()
136
+
137
+ # --- Generate & Download -------------------------------
138
+ st.header("3️⃣ Generate & Download PDF with Captions")
139
+ if st.button("🖋️ Generate Captioned PDF"):
140
+ if not ordered_files:
141
+ st.warning("Upload and reorder at least one image.")
142
+ else:
143
+ now = datetime.now()
144
+ prefix = now.strftime("%Y-%m%d-%I%M%p") + "-" + now.strftime("%a").upper()
145
+ stems = [clean_stem(f.name) for f in ordered_files]
146
+ basename = " - ".join(stems)
147
+ fname = f"{prefix}-{basename}.pdf"
148
+ pdf_bytes = make_image_sized_pdf(ordered_files)
149
+ st.success(f"✅ PDF ready: **{fname}**")
150
+ st.download_button(
151
+ "⬇️ Download PDF", data=pdf_bytes,
152
+ file_name=fname, mime="application/pdf"
153
+ )
154
+ st.markdown("#### Preview of Page 1")
155
+ try:
156
+ import fitz
157
+ doc = fitz.open(stream=pdf_bytes, filetype="pdf")
158
+ pix = doc.load_page(0).get_pixmap(matrix=fitz.Matrix(1.5,1.5))
159
+ st.image(pix.tobytes(), use_container_width=True)
160
+ except Exception:
161
+ st.info("Install `pymupdf` (`fitz`) for preview.")
162
+
163
+ # --- Footer ------------------------------------------------
164
+ st.sidebar.markdown("---")
165
+ st.sidebar.markdown("Built by Aaron C. Wacker • Senior AI Engineer")