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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +152 -92
app.py CHANGED
@@ -1,130 +1,190 @@
 
 
 
 
 
 
 
1
  import gradio as gr
 
 
2
  from selenium import webdriver
3
  from selenium.webdriver.chrome.options import Options
4
  from selenium.webdriver.common.by import By
5
  from selenium.webdriver.support.ui import WebDriverWait
6
  from selenium.webdriver.support import expected_conditions as EC
7
- from PIL import Image
8
- from io import BytesIO
9
- import tempfile
10
- import time
11
- import os
12
- import math
13
 
14
- def render_fullpage_by_scrolling(html_code):
15
- # HTMLコードを一時ファイルに保存
16
- tmp_file = tempfile.NamedTemporaryFile(suffix=".html", delete=False)
17
- tmp_path = tmp_file.name
18
- tmp_file.write(html_code.encode('utf-8'))
19
- tmp_file.close()
20
 
21
- # ヘッドレスChrome(Chromium)起動オプション
 
 
 
 
22
  options = Options()
23
  options.add_argument("--headless")
24
  options.add_argument("--no-sandbox")
25
  options.add_argument("--disable-dev-shm-usage")
26
  options.add_argument("--force-device-scale-factor=1")
27
-
28
  try:
29
  driver = webdriver.Chrome(options=options)
30
-
31
- # 適当な初期サイズでページを読み込む
32
- driver.set_window_size(1200, 800)
33
- driver.get("file://" + tmp_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  # ページロード完了を待機
36
  WebDriverWait(driver, 10).until(
37
  EC.presence_of_element_located((By.TAG_NAME, "body"))
38
  )
39
- # フォントや外部リソース読み込み安定のため待機
40
- time.sleep(2)
41
-
42
- # 必要に応じてスクロールバーを非表示にする(オプション)
43
- driver.execute_script("""
44
- document.documentElement.style.overflow = 'hidden';
45
- document.body.style.overflow = 'hidden';
46
- """)
47
-
48
- # ページ全体の高さ(スクロール量)と、ビューポートの高さを取得
49
- scroll_height = driver.execute_script("return document.body.scrollHeight")
50
- viewport_height = driver.execute_script("return window.innerHeight")
51
-
52
- # スクロール回数を計算
53
- n_screens = math.ceil(scroll_height / viewport_height)
54
 
55
- # 各スクリーンショットを格納するリスト
56
- screenshots = []
57
 
58
- # 最初に最上部へスクロール
59
- driver.execute_script("window.scrollTo(0, 0)")
60
- time.sleep(1)
 
61
 
62
- for i in range(n_screens):
63
- # 現在の位置でスクリーンショットを撮る
64
- png = driver.get_screenshot_as_png()
65
- img = Image.open(BytesIO(png))
66
- screenshots.append(img)
67
 
68
- # 最終スクロールなら抜ける
69
- if i == n_screens - 1:
70
- break
 
71
 
72
- # 次のスクロール位置へ移動
73
- next_scroll = (i + 1) * viewport_height
74
- driver.execute_script(f"window.scrollTo(0, {next_scroll})")
75
- time.sleep(1) # スクロール後の描画待ち
76
 
77
- # -----------------------------------------
78
- # 縦に結合するためのキャンバスを作成
79
- # ページ全体の高さ: scroll_height
80
- # 幅は最初の画像の幅を基準にする
81
- # -----------------------------------------
82
- total_width = screenshots[0].width
83
- total_height = scroll_height
84
 
85
- # 出力先となる空の画像を作る
86
- full_screenshot = Image.new('RGB', (total_width, total_height))
87
 
88
- # 各画像を貼り付けていく
89
- current_y = 0
90
- for i, img in enumerate(screenshots):
91
- # i番目の画像を貼り付ける位置 = i * viewport_height
92
- # ただし最終画像は「残り高さ」分だけ貼り付ければOK
93
-
94
- paste_y = i * viewport_height
95
-
96
- # 残りの高さ (最後だけviewport_heightより小さい可能性)
97
- leftover_height = total_height - paste_y
98
 
99
- if leftover_height < img.height:
100
- # 画像の上から leftover_height 分だけ切り抜く
101
- img = img.crop((0, 0, img.width, leftover_height))
102
 
103
- full_screenshot.paste(img, (0, paste_y))
104
- # paste_yを基準に上から順に貼り付ける
 
 
 
 
 
105
 
106
- # 結合後のfull_screenshotが最終画像
107
- return full_screenshot
108
 
109
- except Exception as e:
110
- # 何かあったときは最小限の画像を返す
111
- return Image.new('RGB', (1, 1), color=(0, 0, 0))
 
112
 
113
- finally:
114
- driver.quit()
115
- if os.path.exists(tmp_path):
116
- os.remove(tmp_path)
117
 
118
- # Gradioインターフェース
119
  iface = gr.Interface(
120
- fn=render_fullpage_by_scrolling,
121
- inputs=gr.Textbox(lines=15, label="HTMLコード入力"),
122
- outputs=gr.Image(type="pil", label="ページ全体のスクリーンショット"),
123
- title="Scrolling Screenshot App",
124
- description=(
125
- "HTMLをヘッドレスブラウザでレンダリングし、"
126
- "スクロールしながら複数回キャプチャを撮って1枚に結合します。"
127
- )
 
 
 
128
  )
129
 
130
  if __name__ == "__main__":
 
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__":