File size: 12,304 Bytes
f996a9b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
import streamlit as st
import os
import tempfile
import zipfile
import shutil
from pdf2image import convert_from_path
from ppt_converter import create_pptx_from_images, reconstruct_pptx_from_zip

# Page configuration
st.set_page_config(
    page_title="PDF/圖片轉可編輯PPTX - 零成本在線版",
    page_icon="📄",
    layout="centered"
)

# Custom CSS for premium aesthetics (Dark Mode / Glassmorphism Feel)
st.markdown("""
    <style>
    /* Main Background & Fonts */
    @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
    
    .stApp {
        font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    }
    
    /* Title Style */
    .title-container {
        text-align: center;
        padding: 1.5rem 0;
        margin-bottom: 2rem;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
    }
    
    /* Tab buttons and cards */
    .css-1n76uvr {
        gap: 0rem;
    }
    
    div.stButton > button:first-child {
        background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
        color: white;
        border: none;
        padding: 0.6rem 2rem;
        border-radius: 8px;
        font-weight: 600;
        letter-spacing: 0.5px;
        transition: all 0.3s ease;
        box-shadow: 0 4px 12px rgba(124, 58, 237, 0.3);
    }
    
    div.stButton > button:first-child:hover {
        transform: translateY(-2px);
        box-shadow: 0 6px 20px rgba(124, 58, 237, 0.4);
        background: linear-gradient(135deg, #4338ca 0%, #6d28d9 100%);
    }
    
    .card {
        background: rgba(255, 255, 255, 0.05);
        border: 1px solid rgba(255, 255, 255, 0.1);
        border-radius: 12px;
        padding: 1.5rem;
        margin-bottom: 1.5rem;
    }
    
    /* Steps styling for Colab */
    .step-number {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 28px;
        height: 28px;
        border-radius: 50%;
        background-color: #4f46e5;
        color: white;
        font-weight: bold;
        margin-right: 8px;
    }
    
    .colab-btn {
        display: inline-block;
        padding: 0.5rem 1.2rem;
        background-color: #f9ab00;
        color: #1e1e1e !important;
        border-radius: 6px;
        font-weight: bold;
        text-decoration: none;
        transition: background-color 0.3s;
        margin: 10px 0;
        box-shadow: 0 4px 10px rgba(249, 171, 0, 0.2);
    }
    .colab-btn:hover {
        background-color: #e39a00;
    }
    </style>
    """, unsafe_allow_html=True)

# Title and introduction
st.markdown("<div class='title-container'><h1>📄 PDF / 圖片 轉 可編輯 PPTX</h1></div>", unsafe_allow_html=True)
st.markdown("""
<div style='text-align: center; margin-top: -20px; margin-bottom: 30px; color: #6b7280; font-size: 1.05rem;'>
將無文字圖層的 AI 扁平簡報(如 NotebookLM)完美轉回 **「圖文分離、背景修復、可編輯」** 的 PowerPoint 檔!
</div>
""", unsafe_allow_html=True)

# Define Tabs
tab1, tab2 = st.tabs(["⚡ 在線極速轉換 (CPU)", "🚀 雲端高精轉換 (Colab GPU 匯入)"])

# -----------------
# Tab 1: Local CPU Mode
# -----------------
with tab1:
    st.markdown("### ⚡ 在線極速轉換")
    st.write("適合一般排版的 PDF 簡報。使用伺服器 CPU 進行 RapidOCR 辨識與 ONNX LaMa 去字修補,隱私安全,一鍵完成。")
    
    uploaded_file = st.file_uploader("選擇上傳您的 PDF 或圖片檔案", type=['pdf', 'png', 'jpg', 'jpeg', 'webp'], key="cpu_uploader")
    
    with st.expander("⚙️ 進階辨識設定"):
        lang_options = ['ch_tra', 'en', 'ja', 'ko', 'ch_sim']
        selected_langs = st.multiselect(
            "選擇 OCR 辨識語言",
            options=lang_options,
            default=['ch_tra', 'en'],
            help="選擇檔案中包含的語言,可大幅提升辨識準確度。"
        )
        high_accuracy = st.toggle(
            "開啟高精度影像增強 (CLAHE)",
            value=False,
            help="開啟後會對圖片進行對比度增強,提升模糊與微小字體的辨識率,但會稍微增加處理時間。"
        )
        
    st.divider()
    
    if uploaded_file is not None:
        if st.button("🚀 開始線上轉換", key="cpu_convert_btn"):
            if not selected_langs:
                st.error("請至少選擇一種辨識語言!")
            else:
                progress_container = st.empty()
                progress_bar = st.progress(0)
                
                with st.spinner('處理中,正在進行 OCR 辨識與背景 AI 修復,請稍候...'):
                    try:
                        with tempfile.TemporaryDirectory() as temp_dir:
                            input_path = os.path.join(temp_dir, uploaded_file.name)
                            output_pptx_path = os.path.join(temp_dir, "output.pptx")
                            
                            # Save uploaded file
                            with open(input_path, "wb") as f:
                                f.write(uploaded_file.getbuffer())
                                
                            ext = os.path.splitext(input_path)[1].lower()
                            image_paths = []
                            
                            if ext == ".pdf":
                                progress_container.info("正在解析 PDF 並轉換為高清圖片...")
                                images = convert_from_path(input_path)
                                total_p = len(images)
                                for idx, img in enumerate(images):
                                    temp_img_path = os.path.join(temp_dir, f"page_{idx}.png")
                                    img.save(temp_img_path, "PNG")
                                    image_paths.append(temp_img_path)
                                    progress_bar.progress(int((idx + 1) / total_p * 20)) # up to 20%
                            elif ext in [".png", ".jpg", ".jpeg", ".webp"]:
                                image_paths = [input_path]
                                progress_bar.progress(20)
                                
                            # Callback for conversion progress
                            def update_conversion_progress(current, total):
                                percent = 20 + int((current / total) * 80)
                                progress_bar.progress(min(percent, 100))
                                progress_container.info(f"正在執行 AI 去字與重組簡報:第 {current} / {total} 頁...")
                                return True
                                
                            # Convert
                            success = create_pptx_from_images(
                                image_paths, 
                                output_pptx_path, 
                                selected_langs, 
                                high_accuracy=high_accuracy,
                                progress_callback=update_conversion_progress
                            )
                            
                            if success:
                                # Read results
                                with open(output_pptx_path, "rb") as f:
                                    pptx_data = f.read()
                                    
                                progress_container.success("🎉 轉換成功!請點擊下方按鈕下載 PPTX。")
                                progress_bar.progress(100)
                                
                                st.download_button(
                                    label="⬇️ 下載可編輯 PPTX 檔案",
                                    data=pptx_data,
                                    file_name=f"{os.path.splitext(uploaded_file.name)[0]}_editable.pptx",
                                    mime="application/vnd.openxmlformats-officedocument.presentationml.presentation",
                                    key="cpu_download"
                                )
                            else:
                                progress_container.error("轉換取消或失敗。")
                                
                    except Exception as e:
                        progress_container.error(f"轉換過程中發生錯誤: {str(e)}")
                        st.exception(e)

# -----------------
# Tab 2: Colab GPU Import Mode
# -----------------
with tab2:
    st.markdown("### 🚀 雲端高精轉換 (Google Colab GPU 分流)")
    st.write("適用於多欄位、包含複雜圖表或超大檔案的簡報。此模式利用多模態大模型進行視覺定位,提供最極致的還原效果,且**完全免費**。")
    
    st.markdown("""
    <div class="card">
        <h4 style="margin-top:0;">💡 簡單三步驟完成高精度轉換:</h4>
        <div style="margin-bottom:10px;">
            <span class="step-number">1</span>
            點擊下方按鈕,在您自己的瀏覽器中打開 Google Colab 筆記本(會自動分配免費的 T4 GPU 算力):
        </div>
        <div style="text-align:center;">
            <a href="https://colab.research.google.com/" target="_blank" class="colab-btn">💻 開啟 Google Colab 轉換腳本</a>
        </div>
        <div style="margin-bottom:10px; margin-top:10px;">
            <span class="step-number">2</span>
            在 Colab 中上傳您的 PDF 檔案,點擊「全部執行」,等待 1~3 分鐘後下載產生的 <code>Pack.zip</code> 壓縮包。
        </div>
        <div>
            <span class="step-number">3</span>
            將下載的 <code>Pack.zip</code> 拖入下方上傳區,即可在一秒內拼裝並下載您的完美可編輯 PPTX!
        </div>
    </div>
    """, unsafe_allow_html=True)
    
    zip_file = st.file_uploader("請上傳 Colab 產出的 Pack.zip 壓縮包", type=['zip'], key="zip_uploader")
    
    if zip_file is not None:
        if st.button("🎨 一秒拼裝簡報", key="zip_assemble_btn"):
            with st.spinner('正在解壓並映射座標...'):
                try:
                    with tempfile.TemporaryDirectory() as temp_dir:
                        zip_path = os.path.join(temp_dir, zip_file.name)
                        output_pptx_path = os.path.join(temp_dir, "reconstructed.pptx")
                        
                        # Save uploaded zip
                        with open(zip_path, "wb") as f:
                            f.write(zip_file.getbuffer())
                            
                        # Reconstruct
                        success = reconstruct_pptx_from_zip(zip_path, output_pptx_path)
                        
                        if success:
                            with open(output_pptx_path, "rb") as f:
                                pptx_data = f.read()
                                
                            st.success("🎉 拼裝完成!請點擊下方按鈕下載 PPTX。")
                            
                            st.download_button(
                                label="⬇️ 下載已拼裝 PPTX 檔案",
                                data=pptx_data,
                                file_name=f"{os.path.splitext(zip_file.name)[0]}_assembled.pptx",
                                mime="application/vnd.openxmlformats-officedocument.presentationml.presentation",
                                key="zip_download"
                            )
                        else:
                            st.error("組裝失敗,請確認上傳的 ZIP 封包格式是否正確。")
                            
                except Exception as e:
                    st.error(f"組裝過程中發生錯誤: {str(e)}")
                    st.exception(e)

# Footer
st.markdown("""
<div style='text-align: center; margin-top: 50px; font-size: 0.8rem; color: #9ca3af; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 15px;'>
PDF/圖片轉可編輯PPTX系統 • 部署於 Hugging Face Spaces (CPU Basic) • 開源免運維版本
</div>
""", unsafe_allow_html=True)