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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +66 -89
app.py CHANGED
@@ -9,7 +9,6 @@ import pandas as pd
9
  import streamlit as st
10
  from PIL import Image
11
  from reportlab.pdfgen import canvas
12
- from reportlab.lib.units import inch
13
  from reportlab.lib.utils import ImageReader
14
 
15
  # --- App Configuration ----------------------------------
@@ -21,7 +20,7 @@ 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, filter/group by shape, reorder panels, and generate a high-definition PDF with smart naming."
25
  )
26
 
27
  # --- Sidebar: Page Settings -----------------------------
@@ -41,7 +40,6 @@ if ratio_choice != "Custom…":
41
  else:
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
  )
@@ -49,106 +47,88 @@ 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:
117
  stem = os.path.splitext(fn)[0]
118
  words += re.findall(r"\w+", stem.lower())
119
- return [w for w, _ in Counter(words).most_common(n)]
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 -------------------------------
@@ -157,25 +137,22 @@ 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")
 
9
  import streamlit as st
10
  from PIL import Image
11
  from reportlab.pdfgen import canvas
 
12
  from reportlab.lib.utils import ImageReader
13
 
14
  # --- App Configuration ----------------------------------
 
20
 
21
  st.title("🖼️ Image → PDF • Comic-Book Layout Generator")
22
  st.markdown(
23
+ "Upload images, choose aspect ratio, filter by shape, reorder visually, and generate a PDF."
24
  )
25
 
26
  # --- Sidebar: Page Settings -----------------------------
 
40
  else:
41
  rw = st.sidebar.number_input("Custom Width Ratio", min_value=1, value=4)
42
  rh = st.sidebar.number_input("Custom Height Ratio", min_value=1, value=3)
 
43
  BASE_WIDTH_PT = st.sidebar.slider(
44
  "Base Page Width (pt)", min_value=400, max_value=1200, value=800, step=100
45
  )
 
47
  page_height = int(BASE_WIDTH_PT * (rh / rw))
48
  st.sidebar.markdown(f"**Page size:** {page_width}×{page_height} pt")
49
 
50
+ # --- Main: Upload, Filter & Reorder -------------------
51
+ st.header("2️⃣ Upload, Filter & Reorder Images")
52
  uploaded = st.file_uploader(
53
+ "📂 Select PNG/JPG images", type=["png","jpg","jpeg"], accept_multiple_files=True
54
  )
55
 
56
+ ordered_files = []
57
  if uploaded:
58
+ # Collect metadata
59
  records = []
60
  for idx, f in enumerate(uploaded):
61
  im = Image.open(f)
62
  w, h = im.size
63
  ar = round(w / h, 2)
64
+ orient = "Square" if 0.9 <= ar <= 1.1 else ("Landscape" if ar > 1.1 else "Portrait")
65
+ records.append({"filename": f.name, "width": w, "height": h,
66
+ "aspect_ratio": ar, "orientation": orient, "order": idx})
 
 
 
 
 
 
 
 
 
 
 
67
  df = pd.DataFrame(records)
68
 
69
+ # Filter by orientation
70
+ dims = st.sidebar.multiselect(
71
+ "Include orientations:", options=["Landscape","Portrait","Square"],
72
+ default=["Landscape","Portrait","Square"]
73
+ )
74
+ df = df[df["orientation"].isin(dims)].reset_index(drop=True)
 
75
 
76
+ # Show metadata table
77
+ st.markdown("#### Image Metadata")
 
78
  st.dataframe(df.style.format({"aspect_ratio": "{:.2f}"}), use_container_width=True)
79
 
80
+ # Visual reorder via Data Editor
81
+ st.markdown("#### Reorder Panels (drag rows or adjust 'order')")
82
+ try:
83
+ edited = st.experimental_data_editor(
84
+ df, num_rows="fixed", use_container_width=True
85
+ )
86
+ ordered_df = edited
87
+ except Exception:
88
+ edited = st.data_editor(
89
+ df,
90
+ column_config={
91
+ "order": st.column_config.NumberColumn(
92
+ "Order", min_value=0, max_value=len(df)-1
93
+ )
94
+ },
95
+ hide_index=True,
96
+ use_container_width=True,
97
+ )
98
+ ordered_df = edited.sort_values("order").reset_index(drop=True)
99
+
100
+ # Map filenames to ordered files
101
  name2file = {f.name: f for f in uploaded}
102
+ ordered_files = [name2file[n] for n in ordered_df["filename"] if n in name2file]
 
 
103
 
104
  # --- PDF Creation Logic ----------------------------------
105
+ def top_n_words(names, n=5):
106
  words = []
107
+ for fn in names:
108
  stem = os.path.splitext(fn)[0]
109
  words += re.findall(r"\w+", stem.lower())
110
+ return [w for w,_ in Counter(words).most_common(n)]
 
111
 
112
  def make_comic_pdf(images, w_pt, h_pt):
113
  buf = io.BytesIO()
114
  c = canvas.Canvas(buf, pagesize=(w_pt, h_pt))
115
  N = len(images)
116
  cols = int(math.ceil(math.sqrt(N)))
117
+ rows = int(math.ceil(N/cols))
118
+ pw, ph = w_pt/cols, h_pt/rows
119
+ for idx, f in enumerate(images):
120
+ im = Image.open(f)
 
121
  iw, ih = im.size
122
+ tar = pw/ph; ar = iw/ih
123
+ if ar>tar:
124
+ nw = int(ih*tar); left=(iw-nw)//2; im=im.crop((left,0,left+nw,ih))
 
 
 
125
  else:
126
+ nh = int(iw/tar); top=(ih-nh)//2; im=im.crop((0,top,iw,top+nh))
127
+ im = im.resize((int(pw),int(ph)), Image.LANCZOS)
128
+ col, row = idx%cols, idx//cols
129
+ x, y = col*pw, h_pt - (row+1)*ph
 
 
 
 
130
  c.drawImage(ImageReader(im), x, y, pw, ph, preserveAspectRatio=False, mask='auto')
131
+ c.showPage(); c.save(); buf.seek(0)
 
 
132
  return buf.getvalue()
133
 
134
  # --- Generate & Download -------------------------------
 
137
  if not ordered_files:
138
  st.warning("Upload and reorder at least one image.")
139
  else:
140
+ date_s = datetime.now().strftime("%Y-%m%d")
141
+ words = top_n_words([f.name for f in ordered_files])
142
  slug = "-".join(words)
143
+ fname = f"{date_s}-{slug}.pdf"
144
+ pdf = make_comic_pdf(ordered_files, page_width, page_height)
145
+ st.success(f"✅ PDF ready: **{fname}**")
146
+ st.download_button("⬇️ Download PDF", data=pdf, file_name=fname, mime="application/pdf")
147
+ st.markdown("#### Preview")
 
 
 
148
  try:
149
  import fitz
150
+ doc = fitz.open(stream=pdf, filetype="pdf")
151
+ pix = doc[0].get_pixmap(matrix=fitz.Matrix(1.5,1.5))
152
  st.image(pix.tobytes(), use_container_width=True)
153
+ except:
154
+ st.info("Install `pymupdf` for preview.")
155
 
156
  # --- Footer ------------------------------------------------
157
  st.sidebar.markdown("---")
158
+ st.sidebar.markdown("Built by Aaron C. Wacker • Senior AI Engineer")