Spaces:
Sleeping
Sleeping
| import fitz # PyMuPDF | |
| from PIL import Image | |
| import gradio as gr | |
| import tempfile | |
| import os | |
| import glob | |
| from gradio import SelectData | |
| PDF_PATH = "presentation.pdf" | |
| MOVIE_PATHS = glob.glob("presentation_movies/*.mp4") | |
| class PDFBrowser: | |
| def __init__(self): | |
| self.doc = None | |
| self.current_page = 0 | |
| self.video_dir = None | |
| self.link_map = {} | |
| def load_all_inputs(self, pdf_file, video_files): | |
| # pdf_file might be a gr.File (has .name) or a str path | |
| pdf_path = pdf_file.name if hasattr(pdf_file, "name") else pdf_file | |
| self.doc = fitz.open(pdf_path) | |
| self.current_page = 0 | |
| # Prepare videos | |
| self.video_dir = tempfile.mkdtemp() | |
| self.video_map = {} | |
| for vf in video_files or []: | |
| src = vf.name if hasattr(vf, "name") else vf | |
| dst = os.path.join(self.video_dir, os.path.basename(src)) | |
| with open(src, "rb") as in_f, open(dst, "wb") as out_f: | |
| out_f.write(in_f.read()) | |
| self.video_map[os.path.basename(src)] = dst | |
| # Scan pages for .mp4 links | |
| self.link_map = {} | |
| for i, page in enumerate(self.doc): | |
| links = [] | |
| for link in page.get_links(): | |
| rect = link.get("from") | |
| path = link.get("file") or link.get("uri") or "" | |
| if path.lower().endswith(".mp4"): | |
| name = os.path.basename(path.replace("run:", "")) | |
| full = self.video_map.get(name) | |
| if full: | |
| links.append((fitz.Rect(rect), full)) | |
| self.link_map[i] = links | |
| return self.render_page() | |
| def render_page(self): | |
| if not self.doc: | |
| return None | |
| page = self.doc[self.current_page] | |
| pix = page.get_pixmap(dpi=150) | |
| img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) | |
| return img | |
| def next_page(self): | |
| if self.doc and self.current_page < len(self.doc) - 1: | |
| self.current_page += 1 | |
| return self.render_page() | |
| def prev_page(self): | |
| if self.doc and self.current_page > 0: | |
| self.current_page -= 1 | |
| return self.render_page() | |
| def handle_click(self, evt: SelectData): | |
| x_click, y_click = evt.index | |
| page = self.doc[self.current_page] | |
| pdf_w, pdf_h = page.rect.width, page.rect.height | |
| scale = 1024 / pdf_w | |
| x_pdf = x_click/scale | |
| y_pdf = pdf_h - (y_click/scale) | |
| pt = fitz.Point(x_pdf, y_pdf) | |
| links = self.link_map.get(self.current_page, []) | |
| if not links: | |
| return None | |
| # 1) try exact hit | |
| for rect, full in links: | |
| if rect.contains(pt): | |
| return full | |
| # 2) fallback: pick the nearest link‐center | |
| # compute (distance, video_path) pairs | |
| distances = [] | |
| for rect, full in links: | |
| cx = (rect.x0 + rect.x1) / 2 | |
| cy = (rect.y0 + rect.y1) / 2 | |
| center = fitz.Point(cx, cy) | |
| distances.append((pt.distance_to(center), full)) | |
| # sort by increasing distance | |
| distances.sort(key=lambda x: x[0]) | |
| best_dist, best_full = distances[0] | |
| # set a PDF‐unit threshold: e.g. half the diagonal of the link rect, | |
| # or a fixed value like 50 pts. | |
| # Here we take half the diagonal of the first rect as a guide: | |
| sample_rect = links[0][0] | |
| diag = ((sample_rect.x1 - sample_rect.x0)**2 + (sample_rect.y1 - sample_rect.y0)**2)**0.5 | |
| max_dist = diag * 0.5 | |
| if best_dist <= max_dist: | |
| return best_full | |
| # still nothing close enough | |
| return None | |
| # build Gradio UI | |
| pdfb = PDFBrowser() | |
| with gr.Blocks() as demo: | |
| image_output = gr.Image(label="PDF Page", interactive=False, width=1024) | |
| video_output = gr.Video(label="Linked Video", interactive=False) | |
| prev_btn = gr.Button("◀ Previous Page", elem_id="prev_btn") | |
| next_btn = gr.Button("Next Page ▶", elem_id="next_btn") | |
| # load on startup | |
| demo.load( | |
| fn=lambda: pdfb.load_all_inputs(PDF_PATH, MOVIE_PATHS), | |
| outputs=image_output | |
| ) | |
| prev_btn.click(pdfb.prev_page, outputs=image_output) | |
| next_btn.click(pdfb.next_page, outputs=image_output) | |
| # **IMPORTANT**: bind the image as an input here | |
| image_output.select( | |
| fn=pdfb.handle_click, | |
| inputs=None, # <-- explicitly list your image | |
| outputs=[video_output] # <-- video_output will get updated | |
| ) | |
| gr.HTML(""" | |
| <script> | |
| document.addEventListener('keydown', function(e) { | |
| if (e.key === 'ArrowRight') { | |
| document.getElementById('next_btn').click(); | |
| } else if (e.key === 'ArrowLeft') { | |
| document.getElementById('prev_btn').click(); | |
| } | |
| }); | |
| </script> | |
| """) | |
| demo.launch() |