tomo2chin2 commited on
Commit
ec228d9
·
verified ·
1 Parent(s): 2519afd

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +112 -198
app.py CHANGED
@@ -171,8 +171,8 @@ def generate_html_from_text(text, temperature=0.3):
171
  - フォント指定:
172
  ```html
173
  <style>
174
- @ import url('https://fonts.googleapis.com/css2?family=Kaisei+Decol&family=Yomogi&family=Zen+Kurenaido&display=swap');
175
- @ import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css');
176
  </style>
177
  ```
178
  ### 5. レイアウト
@@ -184,6 +184,16 @@ def generate_html_from_text(text, temperature=0.3):
184
  - 横幅は100%
185
  - 重要な要素は中央寄り、補足情報は周辺部に配置
186
 
 
 
 
 
 
 
 
 
 
 
187
  ## グラフィックレコーディング表現技法
188
  - テキストと視覚要素のバランスを重視(文字情報の50%以上をFont Awesomeアイコンで視覚的に補完)
189
  - キーワードを囲み線や色で強調し、関連するFont Awesomeアイコンを必ず添える
@@ -353,7 +363,8 @@ def trim_image_whitespace(image, threshold=250, padding=10):
353
  logger.info(f"画像をトリミングしました: 元サイズ {width}x{height} → トリミング後 {trimmed.width}x{trimmed.height}")
354
  return trimmed
355
 
356
- # --- Core Screenshot Logic ---
 
357
  def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0,
358
  trim_whitespace: bool = True) -> Image.Image:
359
  """
@@ -361,14 +372,14 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
361
 
362
  Args:
363
  html_code: The HTML source code string.
364
- extension_percentage: Percentage of extra space to add vertically (e.g., 4 means 4% total).
365
  trim_whitespace: Whether to trim excess whitespace from the image.
366
 
367
  Returns:
368
- A PIL Image object of the screenshot. Returns a 1x1 black image on error.
369
  """
370
- tmp_path = None # 初期化
371
- driver = None # 初期化
372
 
373
  # 1) Save HTML code to a temporary file
374
  try:
@@ -378,7 +389,7 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
378
  logger.info(f"HTML saved to temporary file: {tmp_path}")
379
  except Exception as e:
380
  logger.error(f"Error writing temporary HTML file: {e}")
381
- return Image.new('RGB', (1, 1), color=(0, 0, 0)) # エラー時は黒画像
382
 
383
  # 2) Headless Chrome(Chromium) options
384
  options = Options()
@@ -386,7 +397,6 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
386
  options.add_argument("--no-sandbox")
387
  options.add_argument("--disable-dev-shm-usage")
388
  options.add_argument("--force-device-scale-factor=1")
389
- # Font Awesomeが読み込まれない場合があるため、読み込み待機時間を長く設定
390
  options.add_argument("--disable-features=NetworkService")
391
  options.add_argument("--dns-prefetch-disable")
392
 
@@ -395,7 +405,7 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
395
  driver = webdriver.Chrome(options=options)
396
  logger.info("WebDriver initialized.")
397
 
398
- # 3) 初期ウィンドウサイズを設定(コンテンツの種類に関わらず同じサイズ)
399
  initial_width = 1200
400
  initial_height = 1000
401
  driver.set_window_size(initial_width, initial_height)
@@ -403,97 +413,58 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
403
  logger.info(f"Navigating to {file_url}")
404
  driver.get(file_url)
405
 
406
- # 4) Wait for page load with extended timeout
407
  logger.info("Waiting for body element...")
408
  WebDriverWait(driver, 15).until(
409
  EC.presence_of_element_located((By.TAG_NAME, "body"))
410
  )
411
- logger.info("Body element found. Waiting for potential resource loading...")
412
 
413
- # リソース読み込みの待機時間
414
- time.sleep(3) # 初期待機
415
 
416
- # 5) Font Awesomeと外部リソースの読み込み完了を確認
417
- try:
418
- # 修正後のresource_check_script: arrow functionを廃止し、function式に置換
419
- resource_check_script = """
420
- return new Promise(function(resolve) {
421
- function checkFontAwesome() {
422
- var icons = document.querySelectorAll('.fa, .fas, .far, .fab, [class*=\\"fa-\\" ]');
423
- if (icons.length > 0) {
424
- console.log('Font Awesome icons found:', icons.length);
425
- document.fonts.ready.then(function() {
426
- console.log('All fonts loaded');
427
- setTimeout(resolve, 1000);
428
- });
429
- } else {
430
- document.fonts.ready.then(function() { setTimeout(resolve, 500); });
431
- }
432
- }
433
- if (document.readyState === 'complete') {
434
- checkFontAwesome();
435
- } else {
436
- window.addEventListener('load', checkFontAwesome);
437
- }
438
- });
439
- """
440
-
441
- logger.info("Waiting for Font Awesome and other resources to load...")
442
- driver.set_script_timeout(15) # 15秒のタイムアウト
443
- driver.execute_async_script("const callback = arguments[arguments.length - 1]; " + resource_check_script.strip() + ".then(callback);")
444
- logger.info("Resources loaded successfully")
445
- except Exception as e:
446
- logger.warning(f"Resource loading check failed: {e}. Using fallback wait.")
447
- time.sleep(8) # より長い待機時間を設定
448
 
449
- # 6) スクロールを制御してコンテンツ全体が描画されるようにする
450
- try:
451
- # 修正後のscroll_script: function式に置換
452
- scroll_script = """
453
- return new Promise(function(resolve) {
454
- var height = Math.max(
455
- document.documentElement.scrollHeight,
456
- document.body.scrollHeight
457
- );
458
- var viewportHeight = window.innerHeight;
459
- var scrollStep = Math.floor(viewportHeight * 0.8);
460
- var currentPos = 0;
461
- function scrollDown() {
462
- if (currentPos < height) {
463
- window.scrollTo(0, currentPos);
464
- currentPos += scrollStep;
465
- setTimeout(scrollDown, 100);
466
- } else {
467
- window.scrollTo(0, 0);
468
- setTimeout(resolve, 300);
469
- }
470
- }
471
- scrollDown();
472
- });
473
- """
474
-
475
- logger.info("Ensuring all content is rendered...")
476
- driver.execute_async_script("const callback = arguments[arguments.length - 1]; " + scroll_script.strip() + ".then(callback);")
477
- except Exception as e:
478
- logger.warning(f"Content rendering check failed: {e}")
479
- # スクロールを元の位置に戻す
480
- driver.execute_script("window.scrollTo(0, 0);")
481
- time.sleep(1)
482
-
483
- # 7) Hide scrollbars via CSS
484
- try:
485
- driver.execute_script(
486
- "document.documentElement.style.overflow = 'hidden';"
487
- "document.body.style.overflow = 'hidden';"
488
- )
489
- logger.info("Scrollbars hidden via JS.")
490
- except Exception as e:
491
- logger.warning(f"Could not hide scrollbars via JS: {e}")
492
-
493
- # 8) Get full page dimensions accurately with improved script
494
- try:
495
- # より正確なページ寸法を取得するためのJavaScriptコード
496
- dimensions_script = """
497
  return {
498
  width: Math.max(
499
  document.documentElement.scrollWidth,
@@ -512,137 +483,80 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
512
  document.body ? document.body.clientHeight : 0
513
  )
514
  };
515
- """
516
- dimensions = driver.execute_script(dimensions_script)
517
- scroll_width = dimensions['width']
518
- scroll_height = dimensions['height']
519
-
520
- logger.info(f"Detected dimensions: width={scroll_width}, height={scroll_height}")
521
-
522
- # スクロールして確認する追加の検証
523
- driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
524
- time.sleep(1) # スクロール完了を待つ
525
- driver.execute_script("window.scrollTo(0, 0);")
526
- time.sleep(1) # 元の位置に戻す
527
-
528
- # 再検証
529
- dimensions_after_scroll = driver.execute_script(dimensions_script)
530
- scroll_height = max(scroll_height, dimensions_after_scroll['height'])
531
-
532
- logger.info(f"After scroll check, height={scroll_height}")
533
-
534
- # 最小値と最大値の設定
535
- scroll_width = max(scroll_width, 100) # 最小幅
536
- scroll_height = max(scroll_height, 100) # 最小高さ
537
-
538
- scroll_width = min(scroll_width, 2000) # 最大幅
539
- scroll_height = min(scroll_height, 6000) # 最大高さ
540
-
541
- except Exception as e:
542
- logger.error(f"Error getting page dimensions: {e}")
543
- # フォールバックとしてデフォルト値を設定
544
- scroll_width = 1200
545
- scroll_height = 1000
546
- logger.warning(f"Falling back to dimensions: width={scroll_width}, height={scroll_height}")
547
 
548
- # 9) レイアウト安定化の確認
549
- try:
550
- # 修正後のstability_script: arrow functionをfunction式に置換
551
- stability_script = """
552
- return new Promise(function(resolve) {
553
- var lastHeight = document.body.offsetHeight;
554
- var lastWidth = document.body.offsetWidth;
555
- var stableCount = 0;
556
- function checkStability() {
557
- var currentHeight = document.body.offsetHeight;
558
- var currentWidth = document.body.offsetWidth;
559
- if (currentHeight === lastHeight && currentWidth === lastWidth) {
560
- stableCount++;
561
- if (stableCount >= 3) {
562
- resolve(true);
563
- return;
564
- }
565
- } else {
566
- stableCount = 0;
567
- lastHeight = currentHeight;
568
- lastWidth = currentWidth;
569
- }
570
- setTimeout(checkStability, 200);
571
- }
572
- checkStability();
573
- });
574
- """
575
-
576
- logger.info("Checking layout stability...")
577
- driver.execute_async_script("const callback = arguments[arguments.length - 1]; " + stability_script.strip() + ".then(callback);")
578
- logger.info("Layout is stable")
579
- except Exception as e:
580
- logger.warning(f"Layout stability check failed: {e}")
581
- time.sleep(2) # 追加待機
582
-
583
- # 10) Calculate adjusted height with user-specified margin
584
  adjusted_height = int(scroll_height * (1 + extension_percentage / 100.0))
585
- # Ensure adjusted height is not excessively large or small
586
- adjusted_height = max(adjusted_height, scroll_height, 100) # 最小高さを確保
587
  logger.info(f"Adjusted height calculated: {adjusted_height} (extension: {extension_percentage}%)")
588
 
589
- # 11) Set window size to full page dimensions
590
  adjusted_width = scroll_width
591
  logger.info(f"Resizing window to: width={adjusted_width}, height={adjusted_height}")
592
  driver.set_window_size(adjusted_width, adjusted_height)
593
- logger.info("Waiting for layout stabilization after resize...")
594
 
595
- # レイアウト安定化のための待機
596
- time.sleep(4) # 統一した待機時間
597
-
598
- # 外部リソースの読み込み状態を確認
599
- try:
600
- resource_state = driver.execute_script("""
601
  return {
602
  readyState: document.readyState,
603
  resourcesComplete: !document.querySelector('img:not([complete])') &&
604
- !document.querySelector('link[rel="stylesheet"]:not([loaded])')
605
  };
606
- """)
607
- logger.info(f"Resource state: {resource_state}")
608
-
609
- # ドキュメントの読み込みが完了していない場合、追加で待機
610
- if resource_state['readyState'] != 'complete':
611
- logger.info("Document still loading, waiting additional time...")
612
- time.sleep(2)
613
- except Exception as e:
614
- logger.warning(f"Error checking resource state: {e}")
615
-
616
- # Scroll to top just in case
617
- try:
618
- driver.execute_script("window.scrollTo(0, 0)")
619
  time.sleep(1)
620
- logger.info("Scrolled to top.")
621
- except Exception as e:
622
- logger.warning(f"Could not scroll to top: {e}")
 
 
623
 
624
- # 12) Take screenshot
625
  logger.info("Taking screenshot...")
626
  png = driver.get_screenshot_as_png()
627
  logger.info("Screenshot taken successfully.")
628
 
629
- # Convert to PIL Image
630
  img = Image.open(BytesIO(png))
631
-
632
- # 画像サイズの確認とログ
633
  logger.info(f"Screenshot dimensions: {img.width}x{img.height}")
634
 
635
- # 余白トリミングが有効な場合
636
  if trim_whitespace:
637
- # 余分な空白をトリミング
638
  img = trim_image_whitespace(img, threshold=248, padding=20)
639
  logger.info(f"Trimmed dimensions: {img.width}x{img.height}")
640
 
641
  return img
642
 
643
  except Exception as e:
644
- logger.error(f"An error occurred during screenshot generation: {e}", exc_info=True)
645
- return Image.new('RGB', (1, 1), color=(0, 0, 0)) # Return black 1x1 image on error
646
  finally:
647
  logger.info("Cleaning up...")
648
  if driver:
@@ -657,7 +571,7 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
657
  logger.info(f"Temporary file {tmp_path} removed.")
658
  except Exception as e:
659
  logger.error(f"Error removing temporary file {tmp_path}: {e}")
660
-
661
  # --- Geminiを使った新しい関数 ---
662
  def text_to_screenshot(text: str, extension_percentage: float, temperature: float = 0.3, trim_whitespace: bool = True) -> Image.Image:
663
  """テキストをGemini APIでHTMLに変換し、スクリーンショットを生成する統合関数"""
 
171
  - フォント指定:
172
  ```html
173
  <style>
174
+ @ import url('https ://fonts.googleapis.com/css2?family=Kaisei+Decol&family=Yomogi&family=Zen+Kurenaido&display=swap');
175
+ @ import url('https ://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css');
176
  </style>
177
  ```
178
  ### 5. レイアウト
 
184
  - 横幅は100%
185
  - 重要な要素は中央寄り、補足情報は周辺部に配置
186
 
187
+ ### 重要なレイアウト注意事項
188
+ - Font Awesomeアイコンと文字が重ならないよう適切なマージンを設定する
189
+ - アイコンには必ず`margin-right: 10px;`や`margin-left: 10px;`などの余白を設定する
190
+ - アイコンと文字列は必ず`display: inline-block;`または`display: inline-flex;`で配置し、`align-items: center;`を使用する
191
+ - 複数のアイコンを配置する場合は十分な余白を確保する
192
+ - レイアウト崩れを防ぐために、親要素には`overflow: hidden;`を適用する
193
+ - アイコンの配置はコンテンツフローを妨げないよう注意する
194
+ - カード内でのアイコン配置は、絶対位置指定ではなく相対位置で設定する
195
+ - モバイル表示も考慮し、レスポンシブ対応を徹底する
196
+
197
  ## グラフィックレコーディング表現技法
198
  - テキストと視覚要素のバランスを重視(文字情報の50%以上をFont Awesomeアイコンで視覚的に補完)
199
  - キーワードを囲み線や色で強調し、関連するFont Awesomeアイコンを必ず添える
 
363
  logger.info(f"画像をトリミングしました: 元サイズ {width}x{height} → トリミング後 {trimmed.width}x{trimmed.height}")
364
  return trimmed
365
 
366
+ # 非同期スクリプトを使わず、同期的なスクリプトのみ使用する改善版
367
+
368
  def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0,
369
  trim_whitespace: bool = True) -> Image.Image:
370
  """
 
372
 
373
  Args:
374
  html_code: The HTML source code string.
375
+ extension_percentage: Percentage of extra space to add vertically.
376
  trim_whitespace: Whether to trim excess whitespace from the image.
377
 
378
  Returns:
379
+ A PIL Image object of the screenshot.
380
  """
381
+ tmp_path = None
382
+ driver = None
383
 
384
  # 1) Save HTML code to a temporary file
385
  try:
 
389
  logger.info(f"HTML saved to temporary file: {tmp_path}")
390
  except Exception as e:
391
  logger.error(f"Error writing temporary HTML file: {e}")
392
+ return Image.new('RGB', (1, 1), color=(0, 0, 0))
393
 
394
  # 2) Headless Chrome(Chromium) options
395
  options = Options()
 
397
  options.add_argument("--no-sandbox")
398
  options.add_argument("--disable-dev-shm-usage")
399
  options.add_argument("--force-device-scale-factor=1")
 
400
  options.add_argument("--disable-features=NetworkService")
401
  options.add_argument("--dns-prefetch-disable")
402
 
 
405
  driver = webdriver.Chrome(options=options)
406
  logger.info("WebDriver initialized.")
407
 
408
+ # 3) 初期ウィンドウサイズを設定
409
  initial_width = 1200
410
  initial_height = 1000
411
  driver.set_window_size(initial_width, initial_height)
 
413
  logger.info(f"Navigating to {file_url}")
414
  driver.get(file_url)
415
 
416
+ # 4) ページ読み込み待機
417
  logger.info("Waiting for body element...")
418
  WebDriverWait(driver, 15).until(
419
  EC.presence_of_element_located((By.TAG_NAME, "body"))
420
  )
421
+ logger.info("Body element found. Waiting for resource loading...")
422
 
423
+ # 5) 基本的なリソース読み込み待機 - タイムアウト回避
424
+ time.sleep(3)
425
 
426
+ # Font Awesome読み込み確認 - 非同期を使わない
427
+ logger.info("Checking for Font Awesome resources...")
428
+ fa_count = driver.execute_script("""
429
+ var icons = document.querySelectorAll('.fa, .fas, .far, .fab, [class*="fa-"]');
430
+ return icons.length;
431
+ """)
432
+ logger.info(f"Found {fa_count} Font Awesome elements")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
 
434
+ # リソース読み込み状態を確認
435
+ doc_ready = driver.execute_script("return document.readyState;")
436
+ logger.info(f"Document ready state: {doc_ready}")
437
+
438
+ # Font Awesomeが多い場合は追加待機
439
+ if fa_count > 50:
440
+ logger.info("Many Font Awesome icons detected, waiting additional time")
441
+ time.sleep(2)
442
+
443
+ # 6) コンテンツレンダリングのためのスクロール処理 - 同期的に実行
444
+ logger.info("Performing content rendering scroll...")
445
+ total_height = driver.execute_script("return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);")
446
+ viewport_height = driver.execute_script("return window.innerHeight;")
447
+ scrolls_needed = max(1, total_height // viewport_height)
448
+
449
+ for i in range(scrolls_needed + 1):
450
+ scroll_pos = i * (viewport_height - 200) # オーバーラップさせる
451
+ driver.execute_script(f"window.scrollTo(0, {scroll_pos});")
452
+ time.sleep(0.2) # 短い待機
453
+
454
+ # トップに戻る
455
+ driver.execute_script("window.scrollTo(0, 0);")
456
+ time.sleep(0.5)
457
+ logger.info("Scroll rendering completed")
458
+
459
+ # 7) スクロールバーを非表示に
460
+ driver.execute_script("""
461
+ document.documentElement.style.overflow = 'hidden';
462
+ document.body.style.overflow = 'hidden';
463
+ """)
464
+ logger.info("Scrollbars hidden")
465
+
466
+ # 8) ページの寸法を取得
467
+ dimensions = driver.execute_script("""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  return {
469
  width: Math.max(
470
  document.documentElement.scrollWidth,
 
483
  document.body ? document.body.clientHeight : 0
484
  )
485
  };
486
+ """)
487
+ scroll_width = dimensions['width']
488
+ scroll_height = dimensions['height']
489
+ logger.info(f"Detected dimensions: width={scroll_width}, height={scroll_height}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
 
491
+ # 再検証 - 短いスクロールで再確認
492
+ driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
493
+ time.sleep(0.5)
494
+ driver.execute_script("window.scrollTo(0, 0);")
495
+ time.sleep(0.5)
496
+
497
+ dimensions_after = driver.execute_script("return {height: Math.max(document.documentElement.scrollHeight, document.body.scrollHeight)};")
498
+ scroll_height = max(scroll_height, dimensions_after['height'])
499
+ logger.info(f"After scroll check, height={scroll_height}")
500
+
501
+ # 最小/最大値の設定
502
+ scroll_width = max(scroll_width, 100)
503
+ scroll_height = max(scroll_height, 100)
504
+ scroll_width = min(scroll_width, 2000)
505
+ scroll_height = min(scroll_height, 4000)
506
+
507
+ # 9) レイアウト安定化のための単純な待機 - タイムアウト回避
508
+ logger.info("Waiting for layout stabilization...")
509
+ time.sleep(2)
510
+
511
+ # 10) 高さに余白を追加
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
  adjusted_height = int(scroll_height * (1 + extension_percentage / 100.0))
513
+ adjusted_height = max(adjusted_height, scroll_height, 100)
 
514
  logger.info(f"Adjusted height calculated: {adjusted_height} (extension: {extension_percentage}%)")
515
 
516
+ # 11) ウィンドウサイズを調整
517
  adjusted_width = scroll_width
518
  logger.info(f"Resizing window to: width={adjusted_width}, height={adjusted_height}")
519
  driver.set_window_size(adjusted_width, adjusted_height)
520
+ time.sleep(1)
521
 
522
+ # リソース状態を確認 - 同期的スクリプト
523
+ resource_state = driver.execute_script("""
 
 
 
 
524
  return {
525
  readyState: document.readyState,
526
  resourcesComplete: !document.querySelector('img:not([complete])') &&
527
+ !document.querySelector('link[rel="stylesheet"]:not([loaded])')
528
  };
529
+ """)
530
+ logger.info(f"Resource state: {resource_state}")
531
+
532
+ if resource_state['readyState'] != 'complete':
533
+ logger.info("Document still loading, waiting additional time...")
 
 
 
 
 
 
 
 
534
  time.sleep(1)
535
+
536
+ # トップにスクロール
537
+ driver.execute_script("window.scrollTo(0, 0);")
538
+ time.sleep(0.5)
539
+ logger.info("Scrolled to top.")
540
 
541
+ # 12) スクリーンショット取得
542
  logger.info("Taking screenshot...")
543
  png = driver.get_screenshot_as_png()
544
  logger.info("Screenshot taken successfully.")
545
 
546
+ # PIL画像に変換
547
  img = Image.open(BytesIO(png))
 
 
548
  logger.info(f"Screenshot dimensions: {img.width}x{img.height}")
549
 
550
+ # 余白トリミング
551
  if trim_whitespace:
 
552
  img = trim_image_whitespace(img, threshold=248, padding=20)
553
  logger.info(f"Trimmed dimensions: {img.width}x{img.height}")
554
 
555
  return img
556
 
557
  except Exception as e:
558
+ logger.error(f"Error during screenshot generation: {e}")
559
+ return Image.new('RGB', (1, 1), color=(0, 0, 0))
560
  finally:
561
  logger.info("Cleaning up...")
562
  if driver:
 
571
  logger.info(f"Temporary file {tmp_path} removed.")
572
  except Exception as e:
573
  logger.error(f"Error removing temporary file {tmp_path}: {e}")
574
+
575
  # --- Geminiを使った新しい関数 ---
576
  def text_to_screenshot(text: str, extension_percentage: float, temperature: float = 0.3, trim_whitespace: bool = True) -> Image.Image:
577
  """テキストをGemini APIでHTMLに変換し、スクリーンショットを生成する統合関数"""