File size: 4,912 Bytes
c2f1543
6bf5b4d
 
 
 
 
 
 
c2f1543
 
 
6bf5b4d
 
 
 
 
 
 
 
c2f1543
 
 
6bf5b4d
c2f1543
 
6bf5b4d
 
c2f1543
 
 
 
 
 
 
 
 
6bf5b4d
 
 
 
 
c2f1543
 
 
 
6bf5b4d
 
 
c2f1543
6bf5b4d
 
 
 
 
 
c2f1543
 
6bf5b4d
 
 
 
 
 
 
 
 
 
 
 
1540ae9
1eaced3
c2f1543
6bf5b4d
c2f1543
b7401f0
 
c2f1543
ef4e15e
 
 
 
 
 
6bf5b4d
c5779e9
ef4e15e
 
 
 
 
 
 
 
c714c5f
ef4e15e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9797ea5
6bf5b4d
c2f1543
 
6bf5b4d
 
f3c0137
7bb3833
6bf5b4d
c2f1543
 
6bf5b4d
9797ea5
6bf5b4d
c2f1543
6bf5b4d
 
 
c2f1543
 
 
9797ea5
 
 
1540ae9
9797ea5
 
 
c2f1543
 
 
 
 
 
 
 
 
 
 
00f4b6e
4528c96
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
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()