CoolFace
Apppublic

csrabbit/Bepub3

sourceHugging Faceupdated 7mo agoView on Hugging Face
0likes
app.py204 linesDownload Raw Back to root
1import os2import re3import uuid4import unicodedata5import zipfile6import markdown7import shutil8import gradio as gr9import time10import threading11from PyPDF2 import PdfReader, PdfWriter12from typhoon_ocr import ocr_document13from concurrent.futures import ThreadPoolExecutor14 15# --- Global State ---16is_cancelled = False17rate_lock = threading.Lock()18last_request_time = 019 20def cancel_process():21    global is_cancelled22    is_cancelled = True23    return "🛑 ระบบกำลังหยุดทำงานและสรุปไฟล์เท่าที่ทำได้..."24 25def final_build(content, base_name, user_id):26    if not content.strip(): return None, None, "⚠️ ไม่มีเนื้อหา"27    clean = re.sub(r'\n\*\s*\d+\s*\n|\*\s*\d+\s*\*|\'', '', content)28    clean = re.sub(r'\n\s*\n', '\n\n', clean)29    sections = clean.split('--- Page Break ---')30    build_dir = f"build_{user_id}"31    if os.path.exists(build_dir): shutil.rmtree(build_dir)32    os.makedirs(f'{build_dir}/OEBPS', exist_ok=True)33    os.makedirs(f'{build_dir}/META-INF', exist_ok=True)34    with open(f'{build_dir}/mimetype', 'w') as f: f.write('application/epub+zip')35    with open(f'{build_dir}/META-INF/container.xml', 'w') as f:36        f.write('<?xml version="1.0"?><container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"><rootfiles><rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/></rootfiles></container>')37    38    manifest, spine = "", ""39    for i, sec in enumerate(sections):40        if not sec.strip(): continue41        html = markdown.markdown(sec.strip())42        f_name = f'p{i}.xhtml'43        with open(f'{build_dir}/OEBPS/{f_name}', 'w', encoding='utf-8') as f:44            f.write(f'<html><body>{html}</body></html>')45        manifest += f'<item id="p{i}" href="{f_name}" media-type="application/xhtml+xml"/>\n'46        spine += f'<itemref idref="p{i}"/>\n'47    48    with open(f'{build_dir}/OEBPS/content.opf', 'w', encoding='utf-8') as f:49        f.write(f'<?xml version="1.0"?><package xmlns="http://www.idpf.org/2007/opf" version="2.0"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:title>{base_name}</dc:title><dc:language>th</dc:language></metadata><manifest>{manifest}<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/></manifest><spine toc="ncx">{spine}</spine></package>')50    51    epub_name = f"{base_name}_{user_id}.epub"52    txt_name = f"{base_name}_{user_id}.txt"53    with open(txt_name, "w", encoding="utf-8-sig") as f: f.write(content)54    55    with zipfile.ZipFile(epub_name, 'w', zipfile.ZIP_DEFLATED) as z:56        for r, d, fs in os.walk(build_dir):57            for file in fs:58                file_path = os.path.join(r, file)59                z.write(file_path, os.path.relpath(file_path, build_dir))60    61    shutil.rmtree(build_dir)62    return txt_name, epub_name, "✅ เสร็จสมบูรณ์"63 64def process_and_build_epub(api_key, pdf_file, start_page, end_page, progress=gr.Progress()):65    global is_cancelled66    is_cancelled = False67    user_id = str(uuid.uuid4())[:8]68    if not api_key or not pdf_file: return None, None, "❌ กรุณาใส่ API Key"69    70    os.environ["TYPHOON_OCR_API_KEY"] = api_key71    reader = PdfReader(pdf_file.name)72    total_pages = len(reader.pages)73    s_p = int(max(1, start_page))74    e_p = int(min(total_pages, end_page if end_page > 0 else total_pages))75    target_indices = list(range(s_p - 1, e_p))76    base_name = os.path.basename(pdf_file.name).rsplit('.', 1)[0]77 78    # ฟังก์ชันย่อยสำหรับรัน OCR รายหน้า (จะถูกเรียกแบบขนาน)79    def ocr_worker(idx):80        global last_request_time81        if is_cancelled: return ""82            83        writer = PdfWriter()84        writer.add_page(reader.pages[idx])85        temp_pdf = f"temp_{user_id}_{idx}.pdf"86        with open(temp_pdf, "wb") as f: writer.write(f)87        88        # Traffic Control ทุก 4 วินาที89        with rate_lock:90            current_time = time.time()91            # คำนวณว่าต้องรออีกกี่วินาทีเพื่อให้ห่างจาก Request ล่าสุด 4 วินาที92            wait_time = max(0, 4 - (current_time - last_request_time))93            time.sleep(wait_time)94            last_request_time = time.time()95            96        try:97            page_res = ocr_document(temp_pdf)98            return unicodedata.normalize('NFC', str(page_res)) if page_res else ""99        except Exception as e:100            return f"⚠️ [Error: {str(e)}]"101        finally:102            if os.path.exists(temp_pdf): os.remove(temp_pdf)103 104    try:105        results = []106        with ThreadPoolExecutor(max_workers=5) as executor:107            # ใช้ progress.tqdm เพื่อแสดงความคืบหน้าบน UI108            for res in progress.tqdm(executor.map(ocr_worker, target_indices), total=len(target_indices), desc="⚡ กำลังประมวลผล"):109                if is_cancelled: break110                results.append(res)111        112        full_text_content = "\n\n--- Page Break ---\n\n".join([r for r in results if r])113        return final_build(full_text_content, base_name, user_id)114        115    except Exception as e:116        return None, None, f"❌ Error: {str(e)}"117 118# --- Frontend Layout ---119css = """120.duplicate-btn {121    height: 60px !important; 122    font-size: 1.2em !important;123    border: 2px solid orange !important;124}125"""126 127with gr.Blocks(title="Bepub v2.1.0 Parallel", css=css) as demo:128    gr.Markdown("## Bepub v2.1.0")129    gr.DuplicateButton(130        value="👯 Duplicate Space to Run Privately", 131        variant="secondary",132        elem_classes="duplicate-btn"133    )134    135    with gr.Row():136        # ฝั่งซ้าย: Input137        with gr.Column(scale=1):138            file_input = gr.File(label="1. อัปโหลด PDF", file_types=[".pdf"])139            140            with gr.Group():141                api_input = gr.Textbox(142                    label="2. Typhoon API Key", 143                    type="password", 144                    placeholder="ใส่ API Key ที่นี่..."145                )146                gr.Markdown(147                    '🔑 <a href="https://playground.opentyphoon.ai" target="_blank" rel="noopener noreferrer">คลิกเพื่อรับ Typhoon API Key</a>',148                )149            150            with gr.Row():151                start_page = gr.Number(label="เริ่มหน้า", value=1, precision=0)152                end_page = gr.Number(label="ถึงหน้า", value=1000, precision=0)153            154            with gr.Row():155                btn_start = gr.Button("🚀 เริ่มแปลงไฟล์", variant="primary")156                btn_stop = gr.Button("🛑 หยุดทำงาน", variant="stop")157                158        # ฝั่งขวา: Output (ปรับความสูงที่นี่)159        with gr.Column(scale=1):160            # ปรับ lines=8 เพื่อให้ความสูงรวมของกล่องสถานะใกล้เคียงกับกล่อง File Upload ด้านซ้าย161            status_msg = gr.Textbox(162                label="สถานะ", 163                interactive=False, 164                placeholder="รอการเริ่มทำงาน..."165            )166            167            # กล่องดาวน์โหลดจะใช้พื้นที่ที่เหลือด้านล่าง168            with gr.Row():169                epub_output = gr.File(label="ไฟล์ EPUB")170                txt_output = gr.File(label="ไฟล์ TXT")171 172    # ส่วนการเชื่อมต่อ Function (คงเดิม)173    btn_start.click(174        fn=process_and_build_epub,175        inputs=[api_input, file_input, start_page, end_page],176        outputs=[txt_output, epub_output, status_msg]177    )178    btn_stop.click(fn=cancel_process, outputs=status_msg)179 180    gr.HTML("<hr>")181    with gr.Accordion("📖 วิธีใช้งานและข้อแนะนำ (คลิกเพื่อเปิด)", open=False):182        gr.Markdown("""183        ### 🛠 ขั้นตอนการใช้งาน184        1. (สำคัญ) กด Duplicate Space มาที่ account ตัวเอง เพื่อให้ทำงานโดยไม่ต้องรอคิวคนอื่น185        2. ไปที่ [Typhoon Playground](https://playground.opentyphoon.ai/settings/api-key) คัดลอก Key186        3. Upload ไฟล์ PDF187        4. ระบุหน้าเริ่มต้นและหน้าสิ้นสุด (ระบบตั้งไว้ที่ 1-1000 เป็นค่าเริ่มต้น)188        5. สามารถกดดาวน์โหลดได้ทั้งแบบ **.epub** และ **.txt**189 190        ### ⚠️ ข้อควรรู้191        * ความเร็วขึ้นอยู่กับความหนาแน่นของตัวอักษรใน PDF192        * ไฟล์จะถูกประมวลผลชั่วคราวและถูกลบออก ระบบไม่มีการเก็บข้อมูลส่วนตัวของคุณ193        * หากไฟล์ใหญ่เกินไป แนะนำให้แบ่งทำทีละส่วน (เช่น ครั้งละ 100 หน้า)194        * สามารถยกเลิกกลางคันได้ ระบบจะนำไฟล์เท่าที่ทำเสร็จมาให้ดาวน์โหลด195        """)196    197if __name__ == "__main__":198    demo.queue(default_concurrency_limit=2).launch(199        ssr_mode=False,200        theme=gr.themes.Soft(201            primary_hue="orange", 202            font=[gr.themes.GoogleFont("Noto Sans Thai"), "sans-serif"]203        )204    )