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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -75
app.py CHANGED
@@ -5,6 +5,7 @@ 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
@@ -17,11 +18,14 @@ st.set_page_config(
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 = {
@@ -46,119 +50,136 @@ 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("---")
 
5
  from collections import Counter
6
 
7
  import pandas as pd
8
+ import pytz
9
  import streamlit as st
10
  from PIL import Image
11
  from reportlab.pdfgen import canvas
 
18
  initial_sidebar_state="expanded",
19
  )
20
 
21
+ st.title("🖼️ Image → PDF • Scan, Reorder & Caption Generator")
22
  st.markdown(
23
+ "Scan docs or upload images, filter by orientation, reorder, then generate a captioned PDF matching each images dimensions."
24
  )
25
 
26
+ # --- Session State for Snapshots -----------------------
27
+ st.session_state.setdefault('snapshots', [])
28
+
29
  # --- Sidebar: Page Settings -----------------------------
30
  st.sidebar.header("1️⃣ Page Aspect Ratio & Size")
31
  ratio_map = {
 
50
  page_height = int(BASE_WIDTH_PT * (rh / rw))
51
  st.sidebar.markdown(f"**Preview page size:** {page_width}×{page_height} pt")
52
 
53
+ # --- Main: Document Scan & Image Upload ----------------
54
  st.header("2️⃣ Document Scan & Image Upload")
55
+ # Camera scan
56
+ cam = st.camera_input("📸 Scan Document")
57
+ if cam:
58
+ central = pytz.timezone("US/Central")
59
+ now = datetime.now(central)
60
  prefix = now.strftime("%Y-%m%d-%I%M%p") + "-" + now.strftime("%a").upper()
61
  scan_name = f"{prefix}-scan.png"
62
  with open(scan_name, "wb") as f:
63
+ f.write(cam.getvalue())
64
  st.image(Image.open(scan_name), caption=scan_name, use_container_width=True)
65
+ if scan_name not in st.session_state['snapshots']:
66
+ st.session_state['snapshots'].append(scan_name)
67
 
68
+ # File uploader
69
+ uploads = st.file_uploader(
70
+ "📂 Upload PNG/JPG images", type=["png","jpg","jpeg"], accept_multiple_files=True
71
  )
72
 
73
+ # --- Build combined list --------------------------------
74
+ all_records = []
75
+ # From snapshots
76
+ for idx, path in enumerate(st.session_state['snapshots']):
77
+ im = Image.open(path)
78
+ w, h = im.size
79
+ ar = round(w / h, 2)
80
+ orient = "Square" if 0.9 <= ar <= 1.1 else ("Landscape" if ar > 1.1 else "Portrait")
81
+ all_records.append({
82
+ "filename": os.path.basename(path),
83
+ "source": path,
84
+ "width": w,
85
+ "height": h,
86
+ "aspect_ratio": ar,
87
+ "orientation": orient,
88
+ "order": idx,
89
+ })
90
+ # From uploads
91
+ if uploads:
92
+ for jdx, f in enumerate(uploads, start=len(all_records)):
93
  im = Image.open(f)
94
  w, h = im.size
95
  ar = round(w / h, 2)
96
  orient = "Square" if 0.9 <= ar <= 1.1 else ("Landscape" if ar > 1.1 else "Portrait")
97
+ all_records.append({
98
  "filename": f.name,
99
+ "source": f,
100
  "width": w,
101
  "height": h,
102
  "aspect_ratio": ar,
103
  "orientation": orient,
104
+ "order": jdx,
105
  })
106
+ # DataFrame
107
+ df = pd.DataFrame(all_records)
108
+ # Filter
109
+ dims = st.sidebar.multiselect(
110
+ "Include orientations:", options=["Landscape","Portrait","Square"],
111
+ default=["Landscape","Portrait","Square"]
112
+ )
113
+ df = df[df['orientation'].isin(dims)].reset_index(drop=True)
114
 
115
+ st.markdown("#### Images & Scan Metadata (drag/order)")
116
+ st.dataframe(df.style.format({"aspect_ratio":"{:.2f}"}), use_container_width=True)
117
+ st.markdown("*Drag rows or edit `order` to set PDF page sequence.*")
118
+ # Reordering
119
+ try:
120
+ edited = st.experimental_data_editor(df, num_rows="fixed", use_container_width=True)
121
+ ordered_df = edited
122
+ except Exception:
123
+ edited = st.data_editor(
124
+ df,
125
+ column_config={"order": st.column_config.NumberColumn("Order", min_value=0, max_value=len(df)-1)},
126
+ hide_index=True,
127
+ use_container_width=True,
128
+ )
129
+ ordered_df = edited.sort_values('order').reset_index(drop=True)
 
 
 
130
 
131
+ # Resolve ordered_sources list
132
+ ordered_sources = [row['source'] for _, row in ordered_df.iterrows()]
133
 
134
  # --- Utility: Clean stems -------------------------------
135
  def clean_stem(fn: str) -> str:
136
+ return os.path.splitext(fn)[0].replace('-', ' ').replace('_', ' ')
 
137
 
138
+ # --- PDF Creation: Image Sized + Captions --------------
139
+ def make_image_sized_pdf(sources):
140
+ buf = io.BytesIO()
141
+ c = canvas.Canvas(buf)
142
+ for idx, src in enumerate(sources, start=1):
143
+ im = Image.open(src)
144
  iw, ih = im.size
145
  cap_h = 20
146
+ pw, ph = iw, ih + cap_h
147
+ c.setPageSize((pw, ph))
148
  c.drawImage(ImageReader(im), 0, cap_h, iw, ih, preserveAspectRatio=True, mask='auto')
149
+ caption = clean_stem(os.path.basename(src))
150
+ c.setFont('Helvetica', 12)
151
+ c.drawCentredString(pw/2, cap_h/2, caption)
152
+ c.setFont('Helvetica', 8)
153
+ c.drawRightString(pw-10, 10, str(idx))
154
  c.showPage()
155
+ c.save(); buf.seek(0)
156
+ return buf.getvalue()
 
157
 
158
  # --- Generate & Download -------------------------------
159
+ st.header("3️⃣ Generate & Download PDF")
160
  if st.button("🖋️ Generate Captioned PDF"):
161
+ if not ordered_sources:
162
+ st.warning("No images or scans to include.")
163
  else:
164
+ central = pytz.timezone("US/Central")
165
+ now = datetime.now(central)
166
  prefix = now.strftime("%Y-%m%d-%I%M%p") + "-" + now.strftime("%a").upper()
167
+ stems = [clean_stem(os.path.basename(s)) for s in ordered_sources]
168
+ # limit to 4 stems if too long
169
+ stems = stems[:4]
170
  basename = " - ".join(stems)
171
  fname = f"{prefix}-{basename}.pdf"
172
+ pdf_bytes = make_image_sized_pdf(ordered_sources)
173
  st.success(f"✅ PDF ready: **{fname}**")
174
+ st.download_button("⬇️ Download PDF", data=pdf_bytes, file_name=fname, mime="application/pdf")
175
+ st.markdown("#### Preview Page 1")
 
 
 
176
  try:
177
  import fitz
178
+ doc = fitz.open(stream=pdf_bytes, filetype='pdf')
179
  pix = doc.load_page(0).get_pixmap(matrix=fitz.Matrix(1.5,1.5))
180
  st.image(pix.tobytes(), use_container_width=True)
181
+ except:
182
+ st.info("Install `pymupdf` for preview.")
183
 
184
  # --- Footer ------------------------------------------------
185
  st.sidebar.markdown("---")