tomo2chin2 commited on
Commit
e59fbe0
·
verified ·
1 Parent(s): 5a40515

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +64 -102
app.py CHANGED
@@ -9,60 +9,16 @@ from io import BytesIO
9
  import tempfile
10
  import time
11
  import os
 
12
 
13
- def find_vertical_overlap(img_top: Image.Image, img_bottom: Image.Image) -> int:
14
- """
15
- 2つの画像(img_topの下端 と img_bottomの上端)で、
16
- 連続して同じピクセルがどのくらいあるか(行数)を返す。
17
-
18
- 前提:
19
- - 画像の幅は同じと仮定
20
- - 下端から上端へ向けて、どこまで連続して行が一致するかを判定する
21
- """
22
- width = min(img_top.width, img_bottom.width)
23
- height_top = img_top.height
24
- height_bottom = img_bottom.height
25
-
26
- # ピクセルアクセス用
27
- pix_top = img_top.load()
28
- pix_bottom = img_bottom.load()
29
-
30
- # 重複の最大可能行数
31
- max_overlap = min(height_top, height_bottom)
32
-
33
- overlap_count = 0
34
- # 下から上へ連続一致をチェック
35
- for offset in range(max_overlap):
36
- # img_topの bottom-(offset+1) 行と、img_bottomの offset 行を比較
37
- row_matched = True
38
- y_top = height_top - 1 - offset
39
- y_bottom = offset
40
-
41
- for x in range(width):
42
- if pix_top[x, y_top] != pix_bottom[x, y_bottom]:
43
- row_matched = False
44
- break
45
-
46
- if row_matched:
47
- overlap_count += 1
48
- else:
49
- break
50
-
51
- return overlap_count
52
-
53
- def render_fullpage_screenshot(html_code):
54
- """
55
- (1) position: fixed / sticky 要素を無効化して、
56
- (2) ページをビューポート単位で複数回スクロールしながらスクショ取得、
57
- (3) 隣接する画像で重複行を検出し、切り詰めて縦方向に結合
58
- """
59
- # 1) HTMLを一時ファイルに保存
60
  tmp_file = tempfile.NamedTemporaryFile(suffix=".html", delete=False)
61
  tmp_path = tmp_file.name
62
  tmp_file.write(html_code.encode('utf-8'))
63
  tmp_file.close()
64
 
65
- # 2) ヘッドレスChrome起動オプション
66
  options = Options()
67
  options.add_argument("--headless")
68
  options.add_argument("--no-sandbox")
@@ -71,79 +27,87 @@ def render_fullpage_screenshot(html_code):
71
 
72
  try:
73
  driver = webdriver.Chrome(options=options)
74
- # 初期ウィンドウサイズを設定
 
75
  driver.set_window_size(1200, 800)
76
  driver.get("file://" + tmp_path)
77
 
78
- # 3) ページロード完了を待つ
79
  WebDriverWait(driver, 10).until(
80
  EC.presence_of_element_located((By.TAG_NAME, "body"))
81
  )
 
82
  time.sleep(2)
83
 
84
- # 4) 固定要素をstaticに書き換えて重複の原因を減らす
85
- driver.execute_script(
86
- """
87
- const elems = document.querySelectorAll('*');
88
- for (const e of elems) {
89
- const style = window.getComputedStyle(e);
90
- if (style.position === 'fixed' || style.position === 'sticky') {
91
- e.style.position = 'static';
92
- }
93
- }
94
- """
95
- )
96
- time.sleep(1)
97
 
98
- # 5) ページの高さなどを取得
99
- viewport_height = driver.execute_script("return window.innerHeight")
100
  scroll_height = driver.execute_script("return document.body.scrollHeight")
 
 
 
 
101
 
102
- # 6) スクロールしながら複数回キャプチャ
103
  screenshots = []
104
- current_position = 0
105
 
106
- while True:
107
- # スクリーンショット取得
 
 
 
 
108
  png = driver.get_screenshot_as_png()
109
  img = Image.open(BytesIO(png))
110
  screenshots.append(img)
111
 
112
- # 最下部まで来たらループ終了
113
- if current_position + viewport_height >= scroll_height:
114
  break
115
 
116
- current_position += viewport_height
117
- driver.execute_script(f"window.scrollTo(0, {current_position})")
118
- time.sleep(1)
 
 
 
 
 
 
 
 
 
119
 
120
- # 7) スクリーンショットを縦方向に結合(重複行を検出して除去)
121
- if not screenshots:
122
- return Image.new('RGB', (1, 1), color=(0, 0, 0)) # 何も撮れてない場合
123
 
124
- # merged に順次結合していく
125
- merged = screenshots[0]
126
- for i in range(1, len(screenshots)):
127
- overlap = find_vertical_overlap(merged, screenshots[i])
128
- # 重複行(overlap)だけを切り落とした領域を追加
129
- if overlap > 0:
130
- # overlap分だけ上から切り落とす
131
- cropped = screenshots[i].crop((0, overlap, screenshots[i].width, screenshots[i].height))
132
- else:
133
- cropped = screenshots[i]
134
 
135
- # 新しい高さ分のキャンバスを作り、mergedとcroppedを貼り付ける
136
- new_height = merged.height + cropped.height
137
- new_img = Image.new('RGB', (merged.width, new_height))
138
- new_img.paste(merged, (0, 0))
139
- new_img.paste(cropped, (0, merged.height))
140
 
141
- merged = new_img
 
142
 
143
- final_image = merged
 
 
 
 
 
 
 
 
144
 
145
  except Exception as e:
146
- # 例外時は1x1黒画像を返す
147
  return Image.new('RGB', (1, 1), color=(0, 0, 0))
148
 
149
  finally:
@@ -151,17 +115,15 @@ def render_fullpage_screenshot(html_code):
151
  if os.path.exists(tmp_path):
152
  os.remove(tmp_path)
153
 
154
- return final_image
155
-
156
  # Gradioインターフェース
157
  iface = gr.Interface(
158
- fn=render_fullpage_screenshot,
159
  inputs=gr.Textbox(lines=15, label="HTMLコード入力"),
160
- outputs=gr.Image(type="pil", label="フルページスクリーンショット"),
161
- title="Scrolling Screenshot (Static + Overlap Removal)",
162
  description=(
163
- "固定要素をstaticに書き換えたうえで、スクロール中に発生する"
164
- "重複領域を画像解析で取り除いて縦方向に結合します。"
165
  )
166
  )
167
 
 
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")
 
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:
 
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