awacke1 commited on
Commit
4019dd0
·
verified ·
1 Parent(s): 7f506fd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -66
app.py CHANGED
@@ -21,10 +21,9 @@ st.set_page_config(
21
 
22
  st.title("🖼️ Image → PDF • Comic-Book Layout Generator")
23
  st.markdown(
24
- "Upload images, choose a page aspect ratio, reorder panels, and generate a highdefinition PDF with smart naming."
25
  )
26
 
27
-
28
  # --- Sidebar: Page Settings -----------------------------
29
  st.sidebar.header("1️⃣ Page Aspect Ratio & Size")
30
  ratio_map = {
@@ -43,38 +42,75 @@ else:
43
  rw = st.sidebar.number_input("Custom Width Ratio", min_value=1, value=4)
44
  rh = st.sidebar.number_input("Custom Height Ratio", min_value=1, value=3)
45
 
46
- # Base page width in points (1pt = 1/72 inch)
47
  BASE_WIDTH_PT = st.sidebar.slider(
48
  "Base Page Width (pt)", min_value=400, max_value=1200, value=800, step=100
49
  )
50
  page_width = BASE_WIDTH_PT
51
  page_height = int(BASE_WIDTH_PT * (rh / rw))
52
-
53
  st.sidebar.markdown(f"**Page size:** {page_width}×{page_height} pt")
54
 
55
-
56
- # --- Main: Upload & Reorder -----------------------------
57
- st.header("2️⃣ Upload & Reorder Images")
58
- uploaded_files = st.file_uploader(
59
  "📂 Select PNG/JPG images", type=["png", "jpg", "jpeg"], accept_multiple_files=True
60
  )
61
 
62
- # Build ordering table
63
- if uploaded_files:
64
- df = pd.DataFrame({"filename": [f.name for f in uploaded_files]})
65
- st.markdown("Drag to reorder panels below:")
66
- ordered = st.data_editor(
67
- df, num_rows="fixed", use_container_width=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  )
69
- # Map back to actual file objects in new order
70
- name2file = {f.name: f for f in uploaded_files}
 
71
  ordered_files = [name2file[n] for n in ordered["filename"] if n in name2file]
72
  else:
73
  ordered_files = []
74
 
75
-
76
  # --- PDF Creation Logic ----------------------------------
77
-
78
  def top_n_words(filenames, n=5):
79
  words = []
80
  for fn in filenames:
@@ -84,82 +120,62 @@ def top_n_words(filenames, n=5):
84
 
85
 
86
  def make_comic_pdf(images, w_pt, h_pt):
87
- buffer = io.BytesIO()
88
- c = canvas.Canvas(buffer, pagesize=(w_pt, h_pt))
89
-
90
  N = len(images)
91
  cols = int(math.ceil(math.sqrt(N)))
92
  rows = int(math.ceil(N / cols))
93
- panel_w = w_pt / cols
94
- panel_h = h_pt / rows
95
-
96
- for idx, img_file in enumerate(images):
97
- im = Image.open(img_file)
98
  iw, ih = im.size
99
- target_ar = panel_w / panel_h
100
  img_ar = iw / ih
101
-
102
- # Center-crop to panel aspect
103
- if img_ar > target_ar:
104
- new_w = int(ih * target_ar)
105
- left = (iw - new_w) // 2
106
- im = im.crop((left, 0, left + new_w, ih))
107
  else:
108
- new_h = int(iw / target_ar)
109
- top = (ih - new_h) // 2
110
- im = im.crop((0, top, iw, top + new_h))
111
-
112
- im = im.resize((int(panel_w), int(panel_h)), Image.LANCZOS)
113
-
114
  col = idx % cols
115
  row = idx // cols
116
- x = col * panel_w
117
- y = h_pt - (row + 1) * panel_h
118
-
119
- c.drawImage(
120
- ImageReader(im), x, y, panel_w, panel_h,
121
- preserveAspectRatio=False, mask='auto'
122
- )
123
-
124
  c.showPage()
125
  c.save()
126
- buffer.seek(0)
127
- return buffer.getvalue()
128
-
129
 
130
  # --- Generate & Download -------------------------------
131
  st.header("3️⃣ Generate & Download PDF")
132
  if st.button("🎉 Generate PDF"):
133
  if not ordered_files:
134
- st.warning("Please upload and order at least one image.")
135
  else:
136
- # Build filename: YYYY-MMdd-top5words.pdf
137
  date_str = datetime.now().strftime("%Y-%m%d")
138
  words = top_n_words([f.name for f in ordered_files], n=5)
139
- slug = "-".join(words)
140
  out_name = f"{date_str}-{slug}.pdf"
141
-
142
- pdf_bytes = make_comic_pdf(ordered_files, page_width, page_height)
143
-
144
  st.success(f"✅ PDF ready: **{out_name}**")
145
  st.download_button(
146
- "⬇️ Download PDF", data=pdf_bytes,
147
  file_name=out_name, mime="application/pdf"
148
  )
149
-
150
- # Preview first page (requires pymupdf)
151
  st.markdown("#### PDF Preview")
152
  try:
153
- import fitz # pymupdf
154
- doc = fitz.open(stream=pdf_bytes, filetype="pdf")
155
  pix = doc[0].get_pixmap(matrix=fitz.Matrix(1.5, 1.5))
156
- st.image(pix.tobytes(), use_column_width=True)
157
  except Exception:
158
  st.info("Install `pymupdf` for live PDF preview.")
159
 
160
-
161
  # --- Footer ------------------------------------------------
162
  st.sidebar.markdown("---")
163
- st.sidebar.markdown(
164
- "Built by Aaron C. Wacker • Senior AI Engineer"
165
- )
 
21
 
22
  st.title("🖼️ Image → PDF • Comic-Book Layout Generator")
23
  st.markdown(
24
+ "Upload images, choose a page aspect ratio, filter/group by shape, reorder panels, and generate a high-definition PDF with smart naming."
25
  )
26
 
 
27
  # --- Sidebar: Page Settings -----------------------------
28
  st.sidebar.header("1️⃣ Page Aspect Ratio & Size")
29
  ratio_map = {
 
42
  rw = st.sidebar.number_input("Custom Width Ratio", min_value=1, value=4)
43
  rh = st.sidebar.number_input("Custom Height Ratio", min_value=1, value=3)
44
 
 
45
  BASE_WIDTH_PT = st.sidebar.slider(
46
  "Base Page Width (pt)", min_value=400, max_value=1200, value=800, step=100
47
  )
48
  page_width = BASE_WIDTH_PT
49
  page_height = int(BASE_WIDTH_PT * (rh / rw))
 
50
  st.sidebar.markdown(f"**Page size:** {page_width}×{page_height} pt")
51
 
52
+ # --- Main: Upload, Inspect & Reorder -------------------
53
+ st.header("2️⃣ Upload, Inspect & Reorder Images")
54
+ uploaded = st.file_uploader(
 
55
  "📂 Select PNG/JPG images", type=["png", "jpg", "jpeg"], accept_multiple_files=True
56
  )
57
 
58
+ if uploaded:
59
+ # Collect metadata
60
+ records = []
61
+ for idx, f in enumerate(uploaded):
62
+ im = Image.open(f)
63
+ w, h = im.size
64
+ ar = round(w / h, 2)
65
+ if ar > 1.1:
66
+ orient = "Landscape"
67
+ elif ar < 0.9:
68
+ orient = "Portrait"
69
+ else:
70
+ orient = "Square"
71
+ records.append({
72
+ "filename": f.name,
73
+ "width": w,
74
+ "height": h,
75
+ "aspect_ratio": ar,
76
+ "orientation": orient,
77
+ "order": idx,
78
+ })
79
+ df = pd.DataFrame(records)
80
+
81
+ # Filter by orientation
82
+ dims = st.sidebar.multiselect(
83
+ "Include orientations:",
84
+ options=["Landscape", "Portrait", "Square"],
85
+ default=["Landscape", "Portrait", "Square"]
86
+ )
87
+ df = df[df["orientation"].isin(dims)]
88
+
89
+ # Show table and allow numeric reordering
90
+ st.markdown("#### Image Metadata")
91
+ if uploaded:
92
+ st.dataframe(df.style.format({"aspect_ratio": "{:.2f}"}), use_container_width=True)
93
+
94
+ st.markdown("#### Reorder Panels")
95
+ if uploaded:
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_dataframe_index=True,
104
+ use_container_width=True,
105
  )
106
+ # sort by user-defined order
107
+ ordered = edited.sort_values("order").reset_index(drop=True)
108
+ name2file = {f.name: f for f in uploaded}
109
  ordered_files = [name2file[n] for n in ordered["filename"] if n in name2file]
110
  else:
111
  ordered_files = []
112
 
 
113
  # --- PDF Creation Logic ----------------------------------
 
114
  def top_n_words(filenames, n=5):
115
  words = []
116
  for fn in filenames:
 
120
 
121
 
122
  def make_comic_pdf(images, w_pt, h_pt):
123
+ buf = io.BytesIO()
124
+ c = canvas.Canvas(buf, pagesize=(w_pt, h_pt))
 
125
  N = len(images)
126
  cols = int(math.ceil(math.sqrt(N)))
127
  rows = int(math.ceil(N / cols))
128
+ pw = w_pt / cols
129
+ ph = h_pt / rows
130
+ for idx, img in enumerate(images):
131
+ im = Image.open(img)
 
132
  iw, ih = im.size
133
+ tar_ar = pw / ph
134
  img_ar = iw / ih
135
+ if img_ar > tar_ar:
136
+ nw = int(ih * tar_ar)
137
+ left = (iw - nw) // 2
138
+ im = im.crop((left, 0, left + nw, ih))
 
 
139
  else:
140
+ nh = int(iw / tar_ar)
141
+ top = (ih - nh) // 2
142
+ im = im.crop((0, top, iw, top + nh))
143
+ im = im.resize((int(pw), int(ph)), Image.LANCZOS)
 
 
144
  col = idx % cols
145
  row = idx // cols
146
+ x = col * pw
147
+ y = h_pt - (row + 1) * ph
148
+ c.drawImage(ImageReader(im), x, y, pw, ph, preserveAspectRatio=False, mask='auto')
 
 
 
 
 
149
  c.showPage()
150
  c.save()
151
+ buf.seek(0)
152
+ return buf.getvalue()
 
153
 
154
  # --- Generate & Download -------------------------------
155
  st.header("3️⃣ Generate & Download PDF")
156
  if st.button("🎉 Generate PDF"):
157
  if not ordered_files:
158
+ st.warning("Upload and reorder at least one image.")
159
  else:
 
160
  date_str = datetime.now().strftime("%Y-%m%d")
161
  words = top_n_words([f.name for f in ordered_files], n=5)
162
+ slug = "-".join(words)
163
  out_name = f"{date_str}-{slug}.pdf"
164
+ pdf_data = make_comic_pdf(ordered_files, page_width, page_height)
 
 
165
  st.success(f"✅ PDF ready: **{out_name}**")
166
  st.download_button(
167
+ "⬇️ Download PDF", data=pdf_data,
168
  file_name=out_name, mime="application/pdf"
169
  )
 
 
170
  st.markdown("#### PDF Preview")
171
  try:
172
+ import fitz
173
+ doc = fitz.open(stream=pdf_data, filetype="pdf")
174
  pix = doc[0].get_pixmap(matrix=fitz.Matrix(1.5, 1.5))
175
+ st.image(pix.tobytes(), use_container_width=True)
176
  except Exception:
177
  st.info("Install `pymupdf` for live PDF preview.")
178
 
 
179
  # --- Footer ------------------------------------------------
180
  st.sidebar.markdown("---")
181
+ st.sidebar.markdown("Built by Aaron C. Wacker")