Spaces:
Sleeping
Sleeping
Download app.py from Besimplestudio/Kdp: direct link, hf CLI and curl.
- Browser
- Download file 5.38 kB
-
https://huggingface.co/spaces/Besimplestudio/Kdp/resolve/main/app.py
- Command line
-
hf download hf://spaces/Besimplestudio/Kdp/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Besimplestudio/Kdp/resolve/main/app.py
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() |