Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,7 +5,6 @@ from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL
|
|
| 5 |
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
| 6 |
from PIL import Image
|
| 7 |
import pillow_heif
|
| 8 |
-
from datetime import datetime
|
| 9 |
import os
|
| 10 |
import io
|
| 11 |
import subprocess
|
|
@@ -19,7 +18,6 @@ def process_image(uploaded_file, compress=False, quality=60):
|
|
| 19 |
try:
|
| 20 |
uploaded_file.seek(0)
|
| 21 |
img = Image.open(uploaded_file)
|
| 22 |
-
# Handle transparency and convert to RGB
|
| 23 |
if img.mode == 'RGBA':
|
| 24 |
background = Image.new('RGB', img.size, (255, 255, 255))
|
| 25 |
background.paste(img, mask=img.split()[3])
|
|
@@ -40,11 +38,10 @@ def process_image(uploaded_file, compress=False, quality=60):
|
|
| 40 |
return None
|
| 41 |
|
| 42 |
# --- CORE REPORT GENERATION LOGIC ---
|
| 43 |
-
def generate_report(image_items, job_details,
|
| 44 |
"""Generates the DOCX and PDF report."""
|
| 45 |
doc = Document()
|
| 46 |
section = doc.sections[0]
|
| 47 |
-
# Set page margins
|
| 48 |
section.left_margin = section.right_margin = Inches(0.5)
|
| 49 |
section.top_margin = section.bottom_margin = Inches(0.5)
|
| 50 |
|
|
@@ -55,66 +52,75 @@ def generate_report(image_items, job_details, layout_style, compress_images, pro
|
|
| 55 |
header_table.columns[0].width = Inches(2.5)
|
| 56 |
header_table.columns[1].width = Inches(5.0)
|
| 57 |
|
| 58 |
-
# Left cell: Logo
|
| 59 |
logo_cell = header_table.cell(0, 0)
|
| 60 |
if os.path.exists("logo.png"):
|
| 61 |
logo_cell.paragraphs[0].add_run().add_picture("logo.png", width=Inches(1.5))
|
| 62 |
|
| 63 |
-
# Right cell: Job Details (now from a single multiline input)
|
| 64 |
info_cell = header_table.cell(0, 1)
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
info_cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
| 69 |
|
| 70 |
num_images = len(image_items)
|
| 71 |
|
| 72 |
-
# ---
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
table.autofit = False
|
| 79 |
-
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
| 80 |
-
# Set column widths to be equal
|
| 81 |
-
table.columns[0].width = Inches(3.7)
|
| 82 |
-
table.columns[1].width = Inches(3.7)
|
| 83 |
-
|
| 84 |
-
# --- Process and place the first image (left) ---
|
| 85 |
-
item1 = image_items[i]
|
| 86 |
-
cell1 = table.cell(0, 0)
|
| 87 |
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
# --- File Saving and Conversion ---
|
| 120 |
doc_io = io.BytesIO()
|
|
@@ -127,7 +133,7 @@ def generate_report(image_items, job_details, layout_style, compress_images, pro
|
|
| 127 |
f.write(doc_io.getvalue())
|
| 128 |
subprocess.run(
|
| 129 |
['libreoffice', '--headless', '--convert-to', 'pdf', '--outdir', '.', 'temp_report.docx'],
|
| 130 |
-
check=True, timeout=
|
| 131 |
)
|
| 132 |
pdf_path = 'temp_report.pdf'
|
| 133 |
if os.path.exists(pdf_path):
|
|
@@ -150,8 +156,7 @@ if 'image_dict' not in st.session_state:
|
|
| 150 |
|
| 151 |
def reset_app():
|
| 152 |
"""Clears all session state and reruns the app."""
|
| 153 |
-
|
| 154 |
-
del st.session_state[key]
|
| 155 |
st.rerun()
|
| 156 |
|
| 157 |
# --- UI Layout ---
|
|
@@ -164,28 +169,20 @@ st.markdown("---")
|
|
| 164 |
# --- Sidebar for Settings ---
|
| 165 |
with st.sidebar:
|
| 166 |
st.header("1. Job Information")
|
| 167 |
-
# A single multi-line input for all job details
|
| 168 |
job_details = st.text_area(
|
| 169 |
"Job Details",
|
| 170 |
"T250014 Walmart 3155 Sault Ste. Marie\n446 Great Northern Rd\nSault Ste. Marie, Ontario, P6B 4Z9",
|
| 171 |
-
height=100
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
st.header("2. Report Options")
|
| 175 |
-
# Simplified layout option
|
| 176 |
-
layout_style = st.radio(
|
| 177 |
-
"Select Layout Style",
|
| 178 |
-
('Two Photos per Page',), # Only one style for now as requested
|
| 179 |
-
help="Choose how photos are arranged in the report."
|
| 180 |
)
|
| 181 |
-
compress_images = st.checkbox("Compress Images", value=True, help="
|
| 182 |
|
| 183 |
-
st.header("
|
| 184 |
generate_button = st.button("Generate Report", type="primary", use_container_width=True)
|
| 185 |
reset_button = st.button("Reset All", use_container_width=True, on_click=reset_app)
|
| 186 |
|
| 187 |
-
# --- Main Area
|
| 188 |
-
st.header("
|
| 189 |
uploaded_files = st.file_uploader(
|
| 190 |
"Upload all your photos here",
|
| 191 |
accept_multiple_files=True,
|
|
@@ -194,30 +191,30 @@ uploaded_files = st.file_uploader(
|
|
| 194 |
)
|
| 195 |
|
| 196 |
if uploaded_files:
|
| 197 |
-
for
|
| 198 |
if file.file_id not in st.session_state.image_dict:
|
| 199 |
-
current_max_order = max([d
|
| 200 |
st.session_state.image_dict[file.file_id] = {
|
| 201 |
'file': file,
|
| 202 |
'description': '',
|
| 203 |
'order': current_max_order + 1
|
| 204 |
}
|
| 205 |
|
| 206 |
-
st.info("Set the order using numbers, then add descriptions for each photo.")
|
| 207 |
|
| 208 |
-
sorted_display_items = sorted(st.session_state.image_dict.values(), key=lambda x: x
|
| 209 |
|
| 210 |
for item_data in sorted_display_items:
|
| 211 |
file_id = item_data['file'].file_id
|
| 212 |
st.markdown("---")
|
| 213 |
-
cols = st.columns([
|
| 214 |
|
| 215 |
with cols[0]:
|
| 216 |
st.image(item_data['file'], use_column_width=True)
|
| 217 |
|
| 218 |
with cols[1]:
|
| 219 |
-
item_data['order'] = st.number_input("Order", min_value=1, value=item_data
|
| 220 |
-
item_data['description'] = st.text_area("Description", value=item_data
|
| 221 |
|
| 222 |
# --- Generation Logic ---
|
| 223 |
if generate_button:
|
|
@@ -227,19 +224,20 @@ if generate_button:
|
|
| 227 |
st.error("Please upload at least one image.")
|
| 228 |
else:
|
| 229 |
items_to_sort = list(st.session_state.image_dict.values())
|
| 230 |
-
sorted_image_items = sorted(items_to_sort, key=lambda x: x
|
| 231 |
|
| 232 |
progress_bar = st.progress(0, text="Starting report generation...")
|
| 233 |
try:
|
| 234 |
-
with st.spinner('Generating your report...
|
|
|
|
| 235 |
doc_io, pdf_io = generate_report(
|
| 236 |
sorted_image_items,
|
| 237 |
job_details,
|
| 238 |
-
|
| 239 |
)
|
| 240 |
progress_bar.success("Report generated successfully!")
|
| 241 |
|
| 242 |
-
st.header("
|
| 243 |
dl_col1, dl_col2 = st.columns(2)
|
| 244 |
dl_col1.download_button(
|
| 245 |
"⬇️ Download Word (.docx)", doc_io, f"HKC_Report.docx", use_container_width=True
|
|
|
|
| 5 |
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
| 6 |
from PIL import Image
|
| 7 |
import pillow_heif
|
|
|
|
| 8 |
import os
|
| 9 |
import io
|
| 10 |
import subprocess
|
|
|
|
| 18 |
try:
|
| 19 |
uploaded_file.seek(0)
|
| 20 |
img = Image.open(uploaded_file)
|
|
|
|
| 21 |
if img.mode == 'RGBA':
|
| 22 |
background = Image.new('RGB', img.size, (255, 255, 255))
|
| 23 |
background.paste(img, mask=img.split()[3])
|
|
|
|
| 38 |
return None
|
| 39 |
|
| 40 |
# --- CORE REPORT GENERATION LOGIC ---
|
| 41 |
+
def generate_report(image_items, job_details, compress_images, progress_bar):
|
| 42 |
"""Generates the DOCX and PDF report."""
|
| 43 |
doc = Document()
|
| 44 |
section = doc.sections[0]
|
|
|
|
| 45 |
section.left_margin = section.right_margin = Inches(0.5)
|
| 46 |
section.top_margin = section.bottom_margin = Inches(0.5)
|
| 47 |
|
|
|
|
| 52 |
header_table.columns[0].width = Inches(2.5)
|
| 53 |
header_table.columns[1].width = Inches(5.0)
|
| 54 |
|
|
|
|
| 55 |
logo_cell = header_table.cell(0, 0)
|
| 56 |
if os.path.exists("logo.png"):
|
| 57 |
logo_cell.paragraphs[0].add_run().add_picture("logo.png", width=Inches(1.5))
|
| 58 |
|
|
|
|
| 59 |
info_cell = header_table.cell(0, 1)
|
| 60 |
+
p_info = info_cell.paragraphs[0]
|
| 61 |
+
p_info.text = job_details
|
| 62 |
+
p_info.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
| 63 |
info_cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
| 64 |
|
| 65 |
num_images = len(image_items)
|
| 66 |
|
| 67 |
+
# --- NEW: Corrected Layout Logic ---
|
| 68 |
+
# Iterate through images, placing two items per page
|
| 69 |
+
for i in range(0, num_images, 2):
|
| 70 |
+
# Add a page break before the next set of items, but not on the first page
|
| 71 |
+
if i > 0:
|
| 72 |
+
doc.add_page_break()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
+
# --- First Item on Page ---
|
| 75 |
+
item1 = image_items[i]
|
| 76 |
+
table1 = doc.add_table(rows=1, cols=2)
|
| 77 |
+
table1.autofit = False
|
| 78 |
+
table1.columns[0].width = Inches(3.0) # Description column
|
| 79 |
+
table1.columns[1].width = Inches(4.5) # Photo column
|
| 80 |
+
|
| 81 |
+
# Add description to the left cell
|
| 82 |
+
desc_cell1 = table1.cell(0, 0)
|
| 83 |
+
desc_cell1.text = item1['description']
|
| 84 |
+
desc_cell1.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
| 85 |
+
|
| 86 |
+
# Add photo to the right cell
|
| 87 |
+
img_stream1 = process_image(item1['file'], compress=compress_images)
|
| 88 |
+
if img_stream1:
|
| 89 |
+
photo_cell1 = table1.cell(0, 1)
|
| 90 |
+
# Must clear the cell before adding a picture to avoid extra paragraphs
|
| 91 |
+
photo_cell1._element.clear_content()
|
| 92 |
+
p = photo_cell1.add_paragraph()
|
| 93 |
+
p.add_run().add_picture(img_stream1, width=Inches(4.4))
|
| 94 |
+
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 95 |
+
|
| 96 |
+
progress_bar.progress((i + 1) / num_images)
|
| 97 |
+
|
| 98 |
+
# Add space between the two items on the page
|
| 99 |
+
doc.add_paragraph()
|
| 100 |
+
|
| 101 |
+
# --- Second Item on Page (if it exists) ---
|
| 102 |
+
if i + 1 < num_images:
|
| 103 |
+
item2 = image_items[i + 1]
|
| 104 |
+
table2 = doc.add_table(rows=1, cols=2)
|
| 105 |
+
table2.autofit = False
|
| 106 |
+
table2.columns[0].width = Inches(3.0)
|
| 107 |
+
table2.columns[1].width = Inches(4.5)
|
| 108 |
+
|
| 109 |
+
# Add description
|
| 110 |
+
desc_cell2 = table2.cell(0, 0)
|
| 111 |
+
desc_cell2.text = item2['description']
|
| 112 |
+
desc_cell2.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
| 113 |
+
|
| 114 |
+
# Add photo
|
| 115 |
+
img_stream2 = process_image(item2['file'], compress=compress_images)
|
| 116 |
+
if img_stream2:
|
| 117 |
+
photo_cell2 = table2.cell(0, 1)
|
| 118 |
+
photo_cell2._element.clear_content()
|
| 119 |
+
p = photo_cell2.add_paragraph()
|
| 120 |
+
p.add_run().add_picture(img_stream2, width=Inches(4.4))
|
| 121 |
+
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 122 |
+
|
| 123 |
+
progress_bar.progress((i + 2) / num_images)
|
| 124 |
|
| 125 |
# --- File Saving and Conversion ---
|
| 126 |
doc_io = io.BytesIO()
|
|
|
|
| 133 |
f.write(doc_io.getvalue())
|
| 134 |
subprocess.run(
|
| 135 |
['libreoffice', '--headless', '--convert-to', 'pdf', '--outdir', '.', 'temp_report.docx'],
|
| 136 |
+
check=True, timeout=120 # Increased timeout for larger reports
|
| 137 |
)
|
| 138 |
pdf_path = 'temp_report.pdf'
|
| 139 |
if os.path.exists(pdf_path):
|
|
|
|
| 156 |
|
| 157 |
def reset_app():
|
| 158 |
"""Clears all session state and reruns the app."""
|
| 159 |
+
st.session_state.clear()
|
|
|
|
| 160 |
st.rerun()
|
| 161 |
|
| 162 |
# --- UI Layout ---
|
|
|
|
| 169 |
# --- Sidebar for Settings ---
|
| 170 |
with st.sidebar:
|
| 171 |
st.header("1. Job Information")
|
|
|
|
| 172 |
job_details = st.text_area(
|
| 173 |
"Job Details",
|
| 174 |
"T250014 Walmart 3155 Sault Ste. Marie\n446 Great Northern Rd\nSault Ste. Marie, Ontario, P6B 4Z9",
|
| 175 |
+
height=100,
|
| 176 |
+
help="Enter all job information here. It will appear in the header."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
)
|
| 178 |
+
compress_images = st.checkbox("Compress Images", value=True, help="Greatly reduces final file size.")
|
| 179 |
|
| 180 |
+
st.header("2. Actions")
|
| 181 |
generate_button = st.button("Generate Report", type="primary", use_container_width=True)
|
| 182 |
reset_button = st.button("Reset All", use_container_width=True, on_click=reset_app)
|
| 183 |
|
| 184 |
+
# --- Main Area ---
|
| 185 |
+
st.header("3. Upload, Arrange, and Describe Images")
|
| 186 |
uploaded_files = st.file_uploader(
|
| 187 |
"Upload all your photos here",
|
| 188 |
accept_multiple_files=True,
|
|
|
|
| 191 |
)
|
| 192 |
|
| 193 |
if uploaded_files:
|
| 194 |
+
for file in uploaded_files:
|
| 195 |
if file.file_id not in st.session_state.image_dict:
|
| 196 |
+
current_max_order = max([d.get('order', 0) for d in st.session_state.image_dict.values()], default=0)
|
| 197 |
st.session_state.image_dict[file.file_id] = {
|
| 198 |
'file': file,
|
| 199 |
'description': '',
|
| 200 |
'order': current_max_order + 1
|
| 201 |
}
|
| 202 |
|
| 203 |
+
st.info("Set the report order using numbers, then add descriptions for each photo.")
|
| 204 |
|
| 205 |
+
sorted_display_items = sorted(st.session_state.image_dict.values(), key=lambda x: x.get('order', 0))
|
| 206 |
|
| 207 |
for item_data in sorted_display_items:
|
| 208 |
file_id = item_data['file'].file_id
|
| 209 |
st.markdown("---")
|
| 210 |
+
cols = st.columns([2, 3])
|
| 211 |
|
| 212 |
with cols[0]:
|
| 213 |
st.image(item_data['file'], use_column_width=True)
|
| 214 |
|
| 215 |
with cols[1]:
|
| 216 |
+
item_data['order'] = st.number_input("Order", min_value=1, value=item_data.get('order', 1), key=f"order_{file_id}")
|
| 217 |
+
item_data['description'] = st.text_area("Description", value=item_data.get('description', ''), key=f"desc_{file_id}")
|
| 218 |
|
| 219 |
# --- Generation Logic ---
|
| 220 |
if generate_button:
|
|
|
|
| 224 |
st.error("Please upload at least one image.")
|
| 225 |
else:
|
| 226 |
items_to_sort = list(st.session_state.image_dict.values())
|
| 227 |
+
sorted_image_items = sorted(items_to_sort, key=lambda x: x.get('order', 0))
|
| 228 |
|
| 229 |
progress_bar = st.progress(0, text="Starting report generation...")
|
| 230 |
try:
|
| 231 |
+
with st.spinner('Generating your report... This may take a moment.'):
|
| 232 |
+
# The layout style is now fixed, so we don't need to pass it as a variable
|
| 233 |
doc_io, pdf_io = generate_report(
|
| 234 |
sorted_image_items,
|
| 235 |
job_details,
|
| 236 |
+
compress_images, progress_bar
|
| 237 |
)
|
| 238 |
progress_bar.success("Report generated successfully!")
|
| 239 |
|
| 240 |
+
st.header("4. Download Your Report")
|
| 241 |
dl_col1, dl_col2 = st.columns(2)
|
| 242 |
dl_col1.download_button(
|
| 243 |
"⬇️ Download Word (.docx)", doc_io, f"HKC_Report.docx", use_container_width=True
|