imagetopdf / app.py
yoon2566's picture
Create app.py
c9f1b5e verified
# Gradio ๋ฐ Pillow ์„ค์น˜ (์ด๋ฏธ ์‹คํ–‰ํ–ˆ๋‹ค๋ฉด ์ƒ๋žต ๊ฐ€๋Šฅ)
# !pip install gradio pillow -q
import gradio as gr
from PIL import Image
import os
import re
import io
import tempfile
# --- ์ด์ „ ์ฝ”๋“œ์—์„œ ๊ฐ€์ ธ์˜จ ํ—ฌํผ ํ•จ์ˆ˜๋“ค (๋ณ€๊ฒฝ ์—†์Œ) ---
def natural_sort_key(s):
name_without_extension = os.path.splitext(s)[0]
return [int(text) if text.isdigit() else text.lower()
for text in re.split(r'(\d+)', name_without_extension)]
def resize_and_pad(img, target_size, background_color=(255, 255, 255)):
original_width, original_height = img.size
target_width, target_height = target_size
img_copy = img.copy()
try:
img_copy.thumbnail((target_width, target_height), Image.Resampling.LANCZOS)
except AttributeError:
img_copy.thumbnail((target_width, target_height), Image.ANTIALIAS)
new_img = Image.new("RGB", target_size, background_color)
paste_x = (target_width - img_copy.width) // 2
paste_y = (target_height - img_copy.height) // 2
new_img.paste(img_copy, (paste_x, paste_y))
return new_img
# --- Gradio ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์œ„ํ•œ ํ•ต์‹ฌ ํ•จ์ˆ˜ (๋ณ€๊ฒฝ ์—†์Œ) ---
def images_to_pdf_gradio(uploaded_files, resize_option, output_filename="converted_images.pdf"):
status_messages = []
if not uploaded_files:
return None, "์˜ค๋ฅ˜: ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”."
file_info_list = []
for file_obj in uploaded_files:
temp_path = ""
original_name = ""
if hasattr(file_obj, 'name') and isinstance(file_obj.name, str):
temp_path = file_obj.name
if hasattr(file_obj, 'orig_name') and isinstance(file_obj.orig_name, str):
original_name = file_obj.orig_name
else:
original_name = os.path.basename(temp_path)
elif isinstance(file_obj, str):
temp_path = file_obj
original_name = os.path.basename(temp_path)
else:
status_messages.append(f"โš ๏ธ ๊ฒฝ๊ณ : ์•Œ ์ˆ˜ ์—†๋Š” ํŒŒ์ผ ๊ฐ์ฒด ํƒ€์ž…: {type(file_obj)}. ์ด ํŒŒ์ผ์€ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.")
continue
file_info_list.append({'path': temp_path, 'orig_name': original_name})
if not file_info_list:
final_status = "\n".join(status_messages) + "\n์˜ค๋ฅ˜: ์œ ํšจํ•œ ํŒŒ์ผ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
return None, final_status
try:
sorted_files_info = sorted(file_info_list, key=lambda f_info: natural_sort_key(f_info['orig_name']))
except Exception as e:
final_status = "\n".join(status_messages) + f"\nํŒŒ์ผ ์ •๋ ฌ ์ค‘ ์˜ค๋ฅ˜: {e}."
return None, final_status
pil_images = []
target_size = None
processed_count = 0
if resize_option == "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์— ๋งž์ถค" and sorted_files_info:
try:
first_file_info = sorted_files_info[0]
with Image.open(first_file_info['path']) as img_for_size_orig:
img_for_size = img_for_size_orig.copy()
if img_for_size.mode == 'RGBA' or img_for_size.mode == 'P':
img_for_size = img_for_size.convert('RGB')
target_size = img_for_size.size
status_messages.append(f"๊ธฐ์ค€ ์ด๋ฏธ์ง€ ํฌ๊ธฐ: {target_size} (์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€ '{first_file_info['orig_name']}' ๊ธฐ์ค€)")
except Exception as e:
status_messages.append(f"โš ๏ธ ๊ฒฝ๊ณ : ์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์„ค์ • ์ค‘ ์˜ค๋ฅ˜ - {e}. ์›๋ณธ ํฌ๊ธฐ๋กœ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.")
resize_option = "์›๋ณธ ํฌ๊ธฐ ์œ ์ง€"
for file_info in sorted_files_info:
original_filename = file_info['orig_name']
temp_filepath = file_info['path']
try:
with Image.open(temp_filepath) as img_orig:
img = img_orig.copy()
if img.mode == 'RGBA' or img.mode == 'P':
img = img.convert('RGB')
if resize_option == "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์— ๋งž์ถค" and target_size:
img = resize_and_pad(img, target_size)
pil_images.append(img)
status_messages.append(f"โœ… ์ฒ˜๋ฆฌ ์™„๋ฃŒ: {original_filename}")
processed_count += 1
except Exception as e:
status_messages.append(f"โŒ ์˜ค๋ฅ˜: '{original_filename}' ์ฒ˜๋ฆฌ ์ค‘ ๋ฌธ์ œ ๋ฐœ์ƒ - {e}")
if not pil_images:
final_status = "\n".join(status_messages) + "\n\n์˜ค๋ฅ˜: PDF๋กœ ๋ณ€ํ™˜ํ•  ์œ ํšจํ•œ ์ด๋ฏธ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
return None, final_status
pdf_buffer = io.BytesIO()
try:
pil_images[0].save(
pdf_buffer,
format="PDF",
save_all=True,
append_images=pil_images[1:],
resolution=100.0
)
pdf_buffer.seek(0)
file_prefix = os.path.splitext(output_filename)[0]
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf", prefix=file_prefix + "_") as tmp_pdf:
tmp_pdf.write(pdf_buffer.getvalue())
tmp_pdf_path = tmp_pdf.name
final_status = "\n".join(status_messages) + f"\n\n๐ŸŽ‰ ์„ฑ๊ณต: ์ด {processed_count}๊ฐœ์˜ ์ด๋ฏธ์ง€๋กœ PDF ์ƒ์„ฑ ์™„๋ฃŒ! ์•„๋ž˜ ๋ฒ„ํŠผ์œผ๋กœ ๋‹ค์šด๋กœ๋“œํ•˜์„ธ์š”."
return tmp_pdf_path, final_status
except Exception as e:
final_status = "\n".join(status_messages) + f"\n\nโŒ ์˜ค๋ฅ˜: PDF ํŒŒ์ผ ์ €์žฅ ์ค‘ ๋ฌธ์ œ ๋ฐœ์ƒ - {e}"
return None, final_status
# --- Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜ (DownloadButton ์ดˆ๊ธฐ ์ƒํƒœ ๋ณ€๊ฒฝ) ---
with gr.Blocks(theme=gr.themes.Default()) as demo:
gr.Markdown(
"""
# ๐Ÿ–ผ๏ธ ์ด๋ฏธ์ง€(PNG, JPG)๋ฅผ PDF๋กœ ๋ณ€ํ™˜ ๐Ÿ“„
์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๊ณ , ํ•„์š”์— ๋”ฐ๋ผ ํฌ๊ธฐ ์กฐ์ ˆ ์˜ต์…˜์„ ์„ ํƒํ•œ ํ›„ 'PDF ์ƒ์„ฑ' ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด์„ธ์š”.
ํŒŒ์ผ์€ ์ด๋ฆ„์ˆœ (์˜ˆ: 1.jpg, 2.png, 10.jpg)์œผ๋กœ ์ •๋ ฌ๋ฉ๋‹ˆ๋‹ค.
"""
)
with gr.Row():
with gr.Column(scale=2):
image_files_input = gr.File(
label="1. ์ด๋ฏธ์ง€ ํŒŒ์ผ ์—…๋กœ๋“œ (๋‹ค์ค‘ ์„ ํƒ ๊ฐ€๋Šฅ)",
file_count="multiple",
file_types=["image"],
)
resize_option_input = gr.Radio(
label="2. ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์กฐ์ ˆ ์˜ต์…˜",
choices=["์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์— ๋งž์ถค", "์›๋ณธ ํฌ๊ธฐ ์œ ์ง€"],
value="์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์— ๋งž์ถค",
)
output_pdf_name_input = gr.Textbox(
label="3. ์ƒ์„ฑ๋  PDF ํŒŒ์ผ ์ด๋ฆ„ (ํ™•์žฅ์ž ํฌํ•จ)",
value="converted_images.pdf",
placeholder="์˜ˆ: my_album.pdf"
)
submit_button = gr.Button("๐Ÿš€ PDF ์ƒ์„ฑ ์‹คํ–‰", variant="primary", elem_id="generate-button")
pdf_download_button = gr.DownloadButton(
"โฌ‡๏ธ ๋ณ‘ํ•ฉ๋œ PDF ๋‹ค์šด๋กœ๋“œ (์ƒ์„ฑ ํ›„ ํ™œ์„ฑํ™”)", # ์ดˆ๊ธฐ ๋ ˆ์ด๋ธ” ๋ณ€๊ฒฝ
value=None,
variant="secondary",
visible=True, # ์ฒ˜์Œ๋ถ€ํ„ฐ ๋ณด์ด๋„๋ก ๋ณ€๊ฒฝ
interactive=False # ์ฒ˜์Œ์—๋Š” ๋น„ํ™œ์„ฑํ™”
# elem_id="download-button-custom-style"
)
with gr.Column(scale=3):
status_message_output = gr.Textbox(
label="๐Ÿ“ ์ฒ˜๋ฆฌ ์ƒํƒœ ๋ฐ ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€",
lines=10,
interactive=False,
show_copy_button=True
)
def process_and_update_ui(uploaded_files, resize_option, output_filename):
actual_pdf_file_path, status_msg = images_to_pdf_gradio(uploaded_files, resize_option, output_filename)
download_button_update_dict = {}
if actual_pdf_file_path:
download_button_update_dict = {
"value": actual_pdf_file_path,
"label": "โฌ‡๏ธ ๋ณ‘ํ•ฉ๋œ PDF ๋‹ค์šด๋กœ๋“œ",
"interactive": True # ํ™œ์„ฑํ™”
}
else:
download_button_update_dict = {
"value": None,
"label": "โฌ‡๏ธ PDF ๋‹ค์šด๋กœ๋“œ (์ƒ์„ฑ ์‹คํŒจ ๋˜๋Š” ํŒŒ์ผ ์—†์Œ)",
"interactive": False # ๋น„ํ™œ์„ฑํ™” ์œ ์ง€
}
return status_msg, gr.update(**download_button_update_dict)
submit_button.click(
fn=process_and_update_ui,
inputs=[image_files_input, resize_option_input, output_pdf_name_input],
outputs=[status_message_output, pdf_download_button]
)
# CSS ์ฃผ์„์€ ์ด์ „๊ณผ ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ (์„ ํƒ ์‚ฌํ•ญ)
# demo.css = """ ... """
gr.Markdown(
"""
---
**๐Ÿ’ก ์‚ฌ์šฉ ํŒ:**
- ํŒŒ์ผ ์ด๋ฆ„์— ์ˆซ์ž๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ ์ž์—ฐ ์ •๋ ฌ๋ฉ๋‹ˆ๋‹ค (์˜ˆ: `image1.jpg`, `image2.jpg`, `image10.jpg`).
- '์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์— ๋งž์ถค' ์˜ต์…˜ ์„ ํƒ ์‹œ, ๋ชจ๋“  ์ด๋ฏธ์ง€๊ฐ€ ์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ๊ฐ€๋กœ/์„ธ๋กœ ํฌ๊ธฐ์— ๋งž์ถฐ์ง‘๋‹ˆ๋‹ค. ๋น„์œจ์€ ์œ ์ง€๋˜๋ฉฐ, ๋‚จ๋Š” ๊ณต๊ฐ„์€ ํฐ์ƒ‰์œผ๋กœ ์ฑ„์›Œ์ง‘๋‹ˆ๋‹ค.
- ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ์ด๋‚˜ ๋งŽ์€ ์ˆ˜์˜ ํŒŒ์ผ์„ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋ฉด ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
"""
)
demo.launch(debug=True)