Kdp / app.py
Besimplestudio's picture
Update app.py
91226a9 verified
Raw History Blame Contribute Delete
5.38 kB
# app.py
import tempfile
from pathlib import Path
from typing import Tuple
import gradio as gr
from docx import Document
from docx.shared import Inches
from pypdf import PdfReader
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import inch
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# Register a standard font (optional)
# pdfmetrics.registerFont(TTFont('DejaVu', 'DejaVuSans.ttf'))
# --- KDP Constants ---
TRIM_SIZES_INCHES = {
"5\" × 8\"": (5.0, 8.0),
"5.5\" × 8.5\"": (5.5, 8.5),
"6\" × 9\" (Most Popular)": (6.0, 9.0),
"7\" × 10\"": (7.0, 10.0),
"8.5\" × 11\"": (8.5, 11.0),
}
GUTTER_MARGINS_INCHES = [
(150, 0.375),
(300, 0.5),
(500, 0.625),
(700, 0.75),
(828, 0.875),
]
def extract_text_from_docx(docx_path: str) -> list:
"""Extract paragraphs from DOCX."""
doc = Document(docx_path)
return [para.text for para in doc.paragraphs if para.text.strip()]
def extract_text_from_pdf(pdf_path: str) -> list:
"""Extract text from PDF (simple)."""
reader = PdfReader(pdf_path)
text = []
for page in reader.pages:
page_text = page.extract_text()
if page_text:
text.extend([line for line in page_text.split("\n") if line.strip()])
return text
def apply_kdp_to_docx(docx_path: str, trim_w: float, trim_h: float, page_count: int, has_bleed: bool) -> str:
doc = Document(docx_path)
section = doc.sections[0]
section.page_width = Inches(trim_w)
section.page_height = Inches(trim_h)
outside = 0.375 if has_bleed else 0.25
gutter = 0.375
for max_p, g in GUTTER_MARGINS_INCHES:
if page_count <= max_p:
gutter = g
break
section.top_margin = Inches(outside)
section.bottom_margin = Inches(outside)
section.left_margin = Inches(gutter)
section.right_margin = Inches(outside)
output_path = Path(tempfile.gettempdir()) / f"KDP_formatted_{Path(docx_path).stem}.docx"
doc.save(output_path)
return str(output_path)
def create_pdf_from_text(text_lines: list, output_path: str, trim_w: float, trim_h: float, has_bleed: bool):
"""Create a simple PDF from text lines."""
page_size = (trim_w * inch, trim_h * inch)
outside_margin = 0.375 if has_bleed else 0.25
doc = SimpleDocTemplate(
output_path,
pagesize=page_size,
leftMargin=gutter_to_inch(len(text_lines)) * inch, # Approximate
rightMargin=outside_margin * inch,
topMargin=outside_margin * inch,
bottomMargin=outside_margin * inch
)
styles = getSampleStyleSheet()
story = []
for line in text_lines:
story.append(Paragraph(line, styles['Normal']))
story.append(Spacer(1, 6))
doc.build(story)
def gutter_to_inch(page_count: int) -> float:
gutter = 0.375
for max_p, g in GUTTER_MARGINS_INCHES:
if page_count <= max_p:
gutter = g
break
return gutter
def process_file(
input_file: str,
trim_size: str,
has_bleed: bool,
page_count: int,
) -> Tuple[str, str, str]:
file_path = Path(input_file)
trim_w, trim_h = TRIM_SIZES_INCHES[trim_size]
# Step 1: Extract text and get a working DOCX
if file_path.suffix.lower() == ".pdf":
text_lines = extract_text_from_pdf(str(file_path))
# Create a new DOCX from text
temp_doc = Document()
for line in text_lines:
temp_doc.add_paragraph(line)
docx_path = Path(tempfile.gettempdir()) / "temp_from_pdf.docx"
temp_doc.save(docx_path)
else:
text_lines = extract_text_from_docx(str(file_path))
docx_path = file_path
# Step 2: Format DOCX
formatted_docx = apply_kdp_to_docx(str(docx_path), trim_w, trim_h, page_count, has_bleed)
# Step 3: Create PDF from text
formatted_pdf = Path(tempfile.gettempdir()) / f"KDP_formatted_{file_path.stem}.pdf"
create_pdf_from_text(text_lines, str(formatted_pdf), trim_w, trim_h, has_bleed)
info = f"Page Count Used: {page_count}\n⚠️ PDF is text-only (no images/formatting)."
return formatted_docx, str(formatted_pdf), info
# --- UI ---
with gr.Blocks() as demo:
gr.Markdown("# 📚 KDP Formatter (HF Spaces Compatible)")
gr.Markdown("Upload `.docx` or `.pdf`. **PDF output is text-only** due to platform limits.")
with gr.Row():
with gr.Column():
input_file = gr.File(file_types=[".docx", ".pdf"])
trim_size = gr.Dropdown(choices=list(TRIM_SIZES_INCHES.keys()), value="6\" × 9\" (Most Popular)")
page_count = gr.Number(value=300, minimum=24, maximum=828, label="Page Count")
has_bleed = gr.Checkbox(label="Include Bleed")
submit = gr.Button("Format")
with gr.Column():
out_docx = gr.File(label="Formatted DOCX")
out_pdf = gr.File(label="Formatted PDF (Text Only)")
info = gr.Textbox(label="Info")
submit.click(
process_file,
[input_file, trim_size, has_bleed, page_count],
[out_docx, out_pdf, info]
)
gr.Markdown("💡 For full PDF formatting, run this app locally with LibreOffice installed.")
if __name__ == "__main__":
demo.launch()