HTMLviewer2_API / app.py
tomo2chin2's picture
Update app.py
26b8c3d verified
raw
history blame
6.43 kB
import os
import math
import time
import tempfile
import gc
from io import BytesIO
import gradio as gr
from PIL import Image
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def setup_driver():
"""
ヘッドレスChromiumブラウザを起動するためのWebDriverを初期化する。
Hugging Face Spacesで動かす場合は、packages.txtにchromium-driverが必要。
"""
options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--force-device-scale-factor=1")
try:
driver = webdriver.Chrome(options=options)
# スタート時の画面サイズを固定しておく
driver.set_window_size(1280, 800)
return driver
except Exception as e:
print(f"WebDriver初期化エラー: {e}")
return None
def calculate_scroll_parameters(driver):
"""
スクロールに必要なパラメータを精密に計算する。
"""
# ビューポートの高さ(表示領域)
viewport_height = driver.execute_script("return window.innerHeight")
# ページ全体の高さ
total_height = driver.execute_script("""
return Math.max(
document.body.scrollHeight,
document.documentElement.scrollHeight
);
""")
# スクロール回数を算出(端数が出たら繰り上げ)
scroll_steps = math.ceil(total_height / viewport_height)
# 画像の重複を少し入れておく(50px程度)と、つなぎ目がズレにくい
overlap_pixels = 50
return viewport_height, total_height, scroll_steps, overlap_pixels
def process_screenshot_tiles(tiles, viewport_height, total_height, overlap):
"""
複数のタイル画像(tiles)を縦に合成して1枚のフルページ画像を作る。
overlap: タイル同士を結合する際に重複させる領域(px)
"""
if not tiles:
return None
# 合成画像の最終サイズ
# 幅 = 最初のタイルの幅, 高さ = ページ全体の高さ
stitched_width = tiles[0].width
stitched_height = total_height
# 新しい画像を作る
stitched = Image.new('RGB', (stitched_width, stitched_height))
y_offset = 0
for i, tile in enumerate(tiles):
# タイルを貼り付ける
stitched.paste(tile, (0, y_offset))
# 最終タイル以外はオーバーラップ分を差し引いて次の貼り付け位置を計算
if i < len(tiles) - 1:
y_offset += tile.height - overlap
else:
# 最終タイルではオーバーラップを引かないで終了
y_offset += tile.height
return stitched
def capture_fullpage_screenshot(driver, html_content, css_content):
"""
与えられたHTMLコードとCSSコードを結合したページを生成し、
ヘッドレスブラウザでフルページスクリーンショットを取得(スクロール結合)。
"""
temp_path = None
try:
# もし不要ならCSS非表示でもOK
# driver.execute_script("document.querySelector('style').style.display='none';")
# 一時的なHTMLファイルを作る
with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as f:
f.write(f"""
<html>
<head>
<meta charset="UTF-8">
<style>
{css_content}
</style>
</head>
<body>
{html_content}
</body>
</html>
""".encode('utf-8'))
temp_path = f.name
# ローカルHTMLを読み込み
driver.get(f"file://{temp_path}")
# ページロード完了を待機
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
time.sleep(1.0) # 追加で待機(フォントや画像の読み込み)
# スクロールパラメータ計算
viewport_height, total_height, scroll_steps, overlap_pixels = calculate_scroll_parameters(driver)
tiles = []
for step in range(scroll_steps):
scroll_y = step * (viewport_height - overlap_pixels)
driver.execute_script(f"window.scrollTo(0, {scroll_y});")
# DOMが再描画されるまで待機
time.sleep(0.4)
# スクリーンショット撮影
screenshot = driver.get_screenshot_as_png()
tile_img = Image.open(BytesIO(screenshot))
tiles.append(tile_img)
# タイルを結合
full_image = process_screenshot_tiles(tiles, viewport_height, total_height, overlap_pixels)
# メモリ解放(念のため)
del tiles
gc.collect()
return full_image, None
except Exception as e:
return None, f"エラー発生: {str(e)}"
finally:
if temp_path and os.path.exists(temp_path):
os.unlink(temp_path)
def gradio_interface(html_input, css_input):
"""
Gradioから呼び出すメイン関数。
"""
driver = setup_driver()
if not driver:
return None, "WebDriver初期化に失敗しました。"
image, error = capture_fullpage_screenshot(driver, html_input, css_input)
driver.quit()
if error:
return None, error
else:
return image, None
# Gradioアプリの定義
iface = gr.Interface(
fn=gradio_interface,
inputs=[
gr.Textbox(label="HTMLコード", lines=15, placeholder="<h1>Hello</h1>..."),
gr.Textbox(label="CSSコード", lines=8, placeholder="body { background-color: #fff; }")
],
outputs=[
gr.Image(label="フルページスクリーンショット"),
gr.Textbox(label="エラーメッセージ", interactive=False)
],
title="フルページスクリーンショット(スクロール結合) App",
description="HTMLとCSSを入力すると、ページ全体をスクロールしながらキャプチャして1枚の画像に結合します。"
)
if __name__ == "__main__":
iface.launch()