tomo2chin2 commited on
Commit
da755ee
·
verified ·
1 Parent(s): 26b8c3d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -181
app.py CHANGED
@@ -1,191 +1,101 @@
1
- import os
2
- import math
3
- import time
4
- import tempfile
5
- import gc
6
- from io import BytesIO
7
-
8
  import gradio as gr
9
- from PIL import Image
10
-
11
  from selenium import webdriver
12
  from selenium.webdriver.chrome.options import Options
13
- from selenium.webdriver.common.by import By
14
- from selenium.webdriver.support.ui import WebDriverWait
15
- from selenium.webdriver.support import expected_conditions as EC
16
-
17
-
18
- def setup_driver():
19
- """
20
- ヘッドレスChromiumブラウザを起動するためのWebDriverを初期化する。
21
- Hugging Face Spacesで動かす場合は、packages.txtにchromium-driverが必要。
22
- """
23
- options = Options()
24
- options.add_argument("--headless")
25
- options.add_argument("--no-sandbox")
26
- options.add_argument("--disable-dev-shm-usage")
27
- options.add_argument("--force-device-scale-factor=1")
28
- try:
29
- driver = webdriver.Chrome(options=options)
30
- # スタート時の画面サイズを固定しておく
31
- driver.set_window_size(1280, 800)
32
- return driver
33
- except Exception as e:
34
- print(f"WebDriver初期化エラー: {e}")
35
- return None
36
-
37
-
38
- def calculate_scroll_parameters(driver):
39
- """
40
- スクロールに必要なパラメータを精密に計算する。
41
- """
42
- # ビューポートの高さ(表示領域)
43
- viewport_height = driver.execute_script("return window.innerHeight")
44
- # ページ全体の高さ
45
- total_height = driver.execute_script("""
46
- return Math.max(
47
- document.body.scrollHeight,
48
- document.documentElement.scrollHeight
49
- );
50
- """)
51
- # スクロール回数を算出(端数が出たら繰り上げ)
52
- scroll_steps = math.ceil(total_height / viewport_height)
53
-
54
- # 画像の重複を少し入れておく(50px程度)と、つなぎ目がズレにくい
55
- overlap_pixels = 50
56
- return viewport_height, total_height, scroll_steps, overlap_pixels
57
-
58
-
59
- def process_screenshot_tiles(tiles, viewport_height, total_height, overlap):
60
- """
61
- 複数のタイル画像(tiles)を縦に合成して1枚のフルページ画像を作る。
62
- overlap: タイル同士を結合する際に重複させる領域(px)
63
- """
64
- if not tiles:
65
- return None
66
-
67
- # 合成画像の最終サイズ
68
- # 幅 = 最初のタイルの幅, 高さ = ページ全体の高さ
69
- stitched_width = tiles[0].width
70
- stitched_height = total_height
71
-
72
- # 新しい画像を作る
73
- stitched = Image.new('RGB', (stitched_width, stitched_height))
74
- y_offset = 0
75
-
76
- for i, tile in enumerate(tiles):
77
- # タイルを貼り付ける
78
- stitched.paste(tile, (0, y_offset))
79
-
80
- # 最終タイル以外はオーバーラップ分を差し引いて次の貼り付け位置を計算
81
- if i < len(tiles) - 1:
82
- y_offset += tile.height - overlap
83
- else:
84
- # 最終タイルではオーバーラップを引かないで終了
85
- y_offset += tile.height
86
-
87
- return stitched
88
 
 
 
 
 
 
 
 
89
 
90
- def capture_fullpage_screenshot(driver, html_content, css_content):
91
- """
92
- 与えられたHTMLコードとCSSコードを結合したページを生成し、
93
- ヘッドレスブラウザでフルページスクリーンショットを取得(スクロール結合)。
94
- """
95
- temp_path = None
96
  try:
97
- # もし不要ならCSS非表示でもOK
98
- # driver.execute_script("document.querySelector('style').style.display='none';")
99
-
100
- # 一時的なHTMLファイルを作る
101
- with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as f:
102
- f.write(f"""
103
- <html>
104
- <head>
105
- <meta charset="UTF-8">
106
- <style>
107
- {css_content}
108
- </style>
109
- </head>
110
- <body>
111
- {html_content}
112
- </body>
113
- </html>
114
- """.encode('utf-8'))
115
- temp_path = f.name
116
-
117
- # ローカルHTMLを読み込み
118
- driver.get(f"file://{temp_path}")
119
-
120
- # ページロード完了を待機
121
- WebDriverWait(driver, 10).until(
122
- EC.presence_of_element_located((By.TAG_NAME, "body"))
123
- )
124
- time.sleep(1.0) # 追加で待機(フォントや画像の読み込み)
125
-
126
- # スクロールパラメータ計算
127
- viewport_height, total_height, scroll_steps, overlap_pixels = calculate_scroll_parameters(driver)
128
-
129
- tiles = []
130
- for step in range(scroll_steps):
131
- scroll_y = step * (viewport_height - overlap_pixels)
132
- driver.execute_script(f"window.scrollTo(0, {scroll_y});")
133
-
134
- # DOMが再描画されるまで待機
135
- time.sleep(0.4)
136
-
137
- # スクリーンショット撮影
138
- screenshot = driver.get_screenshot_as_png()
139
- tile_img = Image.open(BytesIO(screenshot))
140
- tiles.append(tile_img)
141
-
142
- # タイルを結合
143
- full_image = process_screenshot_tiles(tiles, viewport_height, total_height, overlap_pixels)
144
-
145
- # メモリ解放(念のため)
146
- del tiles
147
- gc.collect()
148
-
149
- return full_image, None
150
-
151
- except Exception as e:
152
- return None, f"エラー発生: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  finally:
154
- if temp_path and os.path.exists(temp_path):
155
- os.unlink(temp_path)
156
-
157
-
158
- def gradio_interface(html_input, css_input):
159
- """
160
- Gradioから呼び出すメイン関数。
161
- """
162
- driver = setup_driver()
163
- if not driver:
164
- return None, "WebDriver初期化に失敗しました。"
165
-
166
- image, error = capture_fullpage_screenshot(driver, html_input, css_input)
167
- driver.quit()
168
-
169
- if error:
170
- return None, error
171
- else:
172
- return image, None
173
-
174
-
175
- # Gradioアプリの定義
176
- iface = gr.Interface(
177
- fn=gradio_interface,
178
- inputs=[
179
- gr.Textbox(label="HTMLコード", lines=15, placeholder="<h1>Hello</h1>..."),
180
- gr.Textbox(label="CSSコード", lines=8, placeholder="body { background-color: #fff; }")
181
- ],
182
- outputs=[
183
- gr.Image(label="フルページスクリーンショット"),
184
- gr.Textbox(label="エラーメッセージ", interactive=False)
185
- ],
186
- title="フルページスクリーンショット(スクロール結合) App",
187
- description="HTMLとCSSを入力すると、ページ全体をスクロールしながらキャプチャして1枚の画像に結合します。"
188
  )
189
 
190
  if __name__ == "__main__":
191
- iface.launch()
 
1
+ import os, tempfile, time
 
 
 
 
 
 
2
  import gradio as gr
 
 
3
  from selenium import webdriver
4
  from selenium.webdriver.chrome.options import Options
5
+ from PIL import Image
6
+ from io import BytesIO
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ def html_to_screenshot(html_code: str) -> Image.Image:
9
+ # Configure Selenium to use headless Chrome
10
+ chrome_options = Options()
11
+ chrome_options.add_argument("--headless")
12
+ chrome_options.add_argument("--no-sandbox")
13
+ chrome_options.add_argument("--disable-dev-shm-usage")
14
+ chrome_options.add_argument("--disable-gpu")
15
 
16
+ # Launch headless Chrome
17
+ driver = webdriver.Chrome(options=chrome_options)
 
 
 
 
18
  try:
19
+ # Write HTML code to a temporary file
20
+ tmp_file = tempfile.NamedTemporaryFile(suffix=".html", delete=False)
21
+ tmp_path = tmp_file.name
22
+ tmp_file.write(html_code.encode('utf-8'))
23
+ tmp_file.close()
24
+ driver.get(f"file://{tmp_path}")
25
+ time.sleep(1) # allow any dynamic content to load if needed
26
+
27
+ # Get total page dimensions
28
+ total_width = driver.execute_script(
29
+ "return Math.max(document.body.scrollWidth, document.documentElement.scrollWidth);")
30
+ total_height = driver.execute_script(
31
+ "return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);")
32
+ # Set the browser window to full content width and a fixed viewport height
33
+ viewport_height = 1000 # px
34
+ driver.set_window_size(total_width, viewport_height)
35
+
36
+ # Mark all fixed or sticky elements
37
+ driver.execute_script(
38
+ "document.querySelectorAll('*').forEach(el => {"
39
+ " const pos = window.getComputedStyle(el).position;"
40
+ " if(pos === 'fixed' || pos === 'sticky') { el.setAttribute('data-fixed', 'true'); }"
41
+ "});")
42
+ # Screenshot the top of the page (with sticky elements visible)
43
+ screenshots = []
44
+ png_data = driver.get_screenshot_as_png()
45
+ screenshots.append(Image.open(BytesIO(png_data)))
46
+
47
+ # Hide sticky/fixed elements before taking further screenshots
48
+ driver.execute_script(
49
+ "document.querySelectorAll('[data-fixed=\"true\"]').forEach(el => el.style.visibility='hidden');")
50
+
51
+ # Scroll and capture screenshots until reaching the bottom
52
+ pixels_scrolled = viewport_height
53
+ while True:
54
+ if pixels_scrolled >= total_height:
55
+ break # done if we've covered the whole height
56
+ driver.execute_script(f"window.scrollTo(0, {pixels_scrolled});")
57
+ time.sleep(0.2)
58
+ # Check actual scroll position in case we hit the bottom
59
+ current_offset = driver.execute_script("return window.pageYOffset;")
60
+ if current_offset < pixels_scrolled:
61
+ # Adjust if we couldn't scroll the full amount (at bottom of page)
62
+ current_offset = total_height - viewport_height
63
+ driver.execute_script(f"window.scrollTo(0, {current_offset});")
64
+ time.sleep(0.1)
65
+ # Capture screenshot at the current offset
66
+ png_data = driver.get_screenshot_as_png()
67
+ screenshots.append(Image.open(BytesIO(png_data)))
68
+ # Prepare for next iteration
69
+ pixels_scrolled = current_offset + viewport_height
70
+
71
+ # Stitch screenshots into one tall image
72
+ # Compute overlap if the last screenshot went beyond the content bottom
73
+ remainder = total_height % viewport_height
74
+ overlap = viewport_height - remainder if remainder != 0 else 0
75
+ if overlap and len(screenshots) > 1:
76
+ # Crop the overlapping top part from the last image
77
+ last_img = screenshots[-1]
78
+ screenshots[-1] = last_img.crop((0, overlap, last_img.width, last_img.height))
79
+ # Combine images vertically
80
+ total_combined_height = sum(img.height for img in screenshots)
81
+ combined_img = Image.new("RGB", (total_width, total_combined_height))
82
+ y = 0
83
+ for img in screenshots:
84
+ combined_img.paste(img, (0, y))
85
+ y += img.height
86
+
87
+ return combined_img
88
  finally:
89
+ driver.quit()
90
+
91
+ # Set up Gradio interface
92
+ interface = gr.Interface(
93
+ fn=html_to_screenshot,
94
+ inputs=gr.Textbox(label="HTML Code", lines=15),
95
+ outputs=gr.Image(type="pil"),
96
+ title="HTML Full-Page Screenshot",
97
+ description="Enter HTML code and generate a full-page screenshot image."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  )
99
 
100
  if __name__ == "__main__":
101
+ interface.launch()