Added logging for GPIO
This commit is contained in:
parent
a1775baa76
commit
25b20fdbbd
@ -2,6 +2,8 @@
|
|||||||
Utilities for managing book library, scanning EPUBs, and extracting metadata.
|
Utilities for managing book library, scanning EPUBs, and extracting metadata.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Dict, Optional
|
from typing import List, Dict, Optional
|
||||||
from dreader import create_ebook_reader
|
from dreader import create_ebook_reader
|
||||||
@ -11,6 +13,8 @@ from PIL import Image
|
|||||||
import ebooklib
|
import ebooklib
|
||||||
from ebooklib import epub
|
from ebooklib import epub
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def scan_book_directory(directory: Path) -> List[Dict[str, str]]:
|
def scan_book_directory(directory: Path) -> List[Dict[str, str]]:
|
||||||
"""
|
"""
|
||||||
@ -44,10 +48,15 @@ def extract_book_metadata(epub_path: Path, include_cover: bool = True) -> Option
|
|||||||
Returns:
|
Returns:
|
||||||
Dictionary with book metadata or None if extraction fails
|
Dictionary with book metadata or None if extraction fails
|
||||||
"""
|
"""
|
||||||
|
start_time = time.time()
|
||||||
try:
|
try:
|
||||||
# Create temporary reader to extract metadata
|
# Create temporary reader to extract metadata
|
||||||
|
reader_start = time.time()
|
||||||
reader = create_ebook_reader(page_size=(400, 600))
|
reader = create_ebook_reader(page_size=(400, 600))
|
||||||
reader.load_epub(str(epub_path))
|
reader.load_epub(str(epub_path))
|
||||||
|
reader_elapsed = time.time() - reader_start
|
||||||
|
|
||||||
|
logger.debug(f"[METADATA] Loaded EPUB {epub_path.name} in {reader_elapsed:.2f}s")
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
'filename': epub_path.name,
|
'filename': epub_path.name,
|
||||||
@ -58,12 +67,19 @@ def extract_book_metadata(epub_path: Path, include_cover: bool = True) -> Option
|
|||||||
|
|
||||||
# Extract cover image if requested - use direct EPUB extraction
|
# Extract cover image if requested - use direct EPUB extraction
|
||||||
if include_cover:
|
if include_cover:
|
||||||
|
cover_start = time.time()
|
||||||
cover_data = extract_cover_from_epub(epub_path)
|
cover_data = extract_cover_from_epub(epub_path)
|
||||||
|
cover_elapsed = time.time() - cover_start
|
||||||
metadata['cover_data'] = cover_data
|
metadata['cover_data'] = cover_data
|
||||||
|
logger.debug(f"[METADATA] Extracted cover from {epub_path.name} in {cover_elapsed:.2f}s")
|
||||||
|
|
||||||
|
total_elapsed = time.time() - start_time
|
||||||
|
logger.info(f"[METADATA] Extracted metadata from '{metadata['title']}' in {total_elapsed:.2f}s")
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"Error extracting metadata from {epub_path}: {e}")
|
||||||
print(f"Error extracting metadata from {epub_path}: {e}")
|
print(f"Error extracting metadata from {epub_path}: {e}")
|
||||||
return {
|
return {
|
||||||
'filename': epub_path.name,
|
'filename': epub_path.name,
|
||||||
@ -128,15 +144,20 @@ def extract_cover_from_epub(epub_path: Path, max_width: int = 300, max_height: i
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Read the EPUB
|
# Read the EPUB
|
||||||
|
read_start = time.time()
|
||||||
book = epub.read_epub(str(epub_path))
|
book = epub.read_epub(str(epub_path))
|
||||||
|
read_elapsed = time.time() - read_start
|
||||||
|
logger.debug(f"[COVER] Read EPUB {epub_path.name} in {read_elapsed:.2f}s")
|
||||||
|
|
||||||
# Look for cover image
|
# Look for cover image
|
||||||
cover_image = None
|
cover_image = None
|
||||||
|
search_start = time.time()
|
||||||
|
|
||||||
# First, try to find item marked as cover
|
# First, try to find item marked as cover
|
||||||
for item in book.get_items():
|
for item in book.get_items():
|
||||||
if item.get_type() == ebooklib.ITEM_COVER:
|
if item.get_type() == ebooklib.ITEM_COVER:
|
||||||
cover_image = Image.open(BytesIO(item.get_content()))
|
cover_image = Image.open(BytesIO(item.get_content()))
|
||||||
|
logger.debug(f"[COVER] Found cover marked as ITEM_COVER in {epub_path.name}")
|
||||||
break
|
break
|
||||||
|
|
||||||
# If not found, look for files with 'cover' in the name
|
# If not found, look for files with 'cover' in the name
|
||||||
@ -146,6 +167,7 @@ def extract_cover_from_epub(epub_path: Path, max_width: int = 300, max_height: i
|
|||||||
name = item.get_name().lower()
|
name = item.get_name().lower()
|
||||||
if 'cover' in name:
|
if 'cover' in name:
|
||||||
cover_image = Image.open(BytesIO(item.get_content()))
|
cover_image = Image.open(BytesIO(item.get_content()))
|
||||||
|
logger.debug(f"[COVER] Found cover by filename in {epub_path.name}")
|
||||||
break
|
break
|
||||||
|
|
||||||
# If still not found, get the first image
|
# If still not found, get the first image
|
||||||
@ -154,26 +176,36 @@ def extract_cover_from_epub(epub_path: Path, max_width: int = 300, max_height: i
|
|||||||
if item.get_type() == ebooklib.ITEM_IMAGE:
|
if item.get_type() == ebooklib.ITEM_IMAGE:
|
||||||
try:
|
try:
|
||||||
cover_image = Image.open(BytesIO(item.get_content()))
|
cover_image = Image.open(BytesIO(item.get_content()))
|
||||||
|
logger.debug(f"[COVER] Using first image as cover in {epub_path.name}")
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
search_elapsed = time.time() - search_start
|
||||||
|
|
||||||
if not cover_image:
|
if not cover_image:
|
||||||
|
logger.debug(f"[COVER] No cover image found in {epub_path.name}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Resize if needed (maintain aspect ratio)
|
# Resize if needed (maintain aspect ratio)
|
||||||
|
process_start = time.time()
|
||||||
if cover_image.width > max_width or cover_image.height > max_height:
|
if cover_image.width > max_width or cover_image.height > max_height:
|
||||||
cover_image.thumbnail((max_width, max_height), Image.Resampling.LANCZOS)
|
cover_image.thumbnail((max_width, max_height), Image.Resampling.LANCZOS)
|
||||||
|
logger.debug(f"[COVER] Resized cover for {epub_path.name}")
|
||||||
|
|
||||||
# Convert to base64
|
# Convert to base64
|
||||||
buffer = BytesIO()
|
buffer = BytesIO()
|
||||||
cover_image.save(buffer, format='PNG')
|
cover_image.save(buffer, format='PNG')
|
||||||
img_bytes = buffer.getvalue()
|
img_bytes = buffer.getvalue()
|
||||||
img_base64 = base64.b64encode(img_bytes).decode('utf-8')
|
img_base64 = base64.b64encode(img_bytes).decode('utf-8')
|
||||||
|
process_elapsed = time.time() - process_start
|
||||||
|
|
||||||
|
logger.debug(f"[COVER] Processed cover for {epub_path.name}: search={search_elapsed:.2f}s, encode={process_elapsed:.2f}s")
|
||||||
|
|
||||||
return img_base64
|
return img_base64
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"Error extracting cover from EPUB {epub_path}: {e}")
|
||||||
print(f"Error extracting cover from EPUB {epub_path}: {e}")
|
print(f"Error extracting cover from EPUB {epub_path}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@ -57,6 +57,16 @@ from PIL import Image
|
|||||||
from .hal import DisplayHAL
|
from .hal import DisplayHAL
|
||||||
from .gesture import TouchEvent as AppTouchEvent, GestureType as AppGestureType
|
from .gesture import TouchEvent as AppTouchEvent, GestureType as AppGestureType
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Try to import GPIO button support (only available on Raspberry Pi)
|
||||||
|
try:
|
||||||
|
from .gpio_buttons import GPIOButtonHandler, load_button_config_from_dict
|
||||||
|
GPIO_BUTTONS_AVAILABLE = True
|
||||||
|
except (ImportError, RuntimeError) as e:
|
||||||
|
GPIO_BUTTONS_AVAILABLE = False
|
||||||
|
logger.debug(f"GPIO buttons not available: {e}")
|
||||||
|
|
||||||
# Import dreader-hal components
|
# Import dreader-hal components
|
||||||
try:
|
try:
|
||||||
from dreader_hal import (
|
from dreader_hal import (
|
||||||
@ -72,8 +82,6 @@ except ImportError as e:
|
|||||||
DREADER_HAL_AVAILABLE = False
|
DREADER_HAL_AVAILABLE = False
|
||||||
_import_error = e
|
_import_error = e
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# Gesture type mapping between dreader-hal and dreader-application
|
# Gesture type mapping between dreader-hal and dreader-application
|
||||||
GESTURE_TYPE_MAP = {
|
GESTURE_TYPE_MAP = {
|
||||||
@ -139,10 +147,16 @@ class HardwareDisplayHAL(DisplayHAL):
|
|||||||
enable_power_monitor: bool = True,
|
enable_power_monitor: bool = True,
|
||||||
shunt_ohms: float = 0.1,
|
shunt_ohms: float = 0.1,
|
||||||
battery_capacity_mah: float = 3000,
|
battery_capacity_mah: float = 3000,
|
||||||
|
gpio_config: Optional[dict] = None,
|
||||||
|
config_file: Optional[str] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initialize hardware HAL.
|
Initialize hardware HAL.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gpio_config: GPIO button configuration dict (optional)
|
||||||
|
config_file: Path to hardware_config.json file (optional, defaults to "hardware_config.json")
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ImportError: If dreader-hal library is not installed
|
ImportError: If dreader-hal library is not installed
|
||||||
"""
|
"""
|
||||||
@ -178,6 +192,36 @@ class HardwareDisplayHAL(DisplayHAL):
|
|||||||
battery_capacity_mah=battery_capacity_mah,
|
battery_capacity_mah=battery_capacity_mah,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# GPIO button handler (optional)
|
||||||
|
self.gpio_handler: Optional[GPIOButtonHandler] = None
|
||||||
|
|
||||||
|
# Load GPIO config from file if specified
|
||||||
|
if config_file or gpio_config is None:
|
||||||
|
config_path = Path(config_file or "hardware_config.json")
|
||||||
|
if config_path.exists():
|
||||||
|
try:
|
||||||
|
with open(config_path, 'r') as f:
|
||||||
|
full_config = json.load(f)
|
||||||
|
gpio_config = full_config
|
||||||
|
logger.info(f"Loaded hardware config from {config_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not load hardware config from {config_path}: {e}")
|
||||||
|
|
||||||
|
# Initialize GPIO buttons if configured
|
||||||
|
if gpio_config and GPIO_BUTTONS_AVAILABLE:
|
||||||
|
try:
|
||||||
|
self.gpio_handler = load_button_config_from_dict(
|
||||||
|
gpio_config,
|
||||||
|
screen_width=width,
|
||||||
|
screen_height=height
|
||||||
|
)
|
||||||
|
if self.gpio_handler:
|
||||||
|
logger.info("GPIO button handler created")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not initialize GPIO buttons: {e}")
|
||||||
|
elif gpio_config and not GPIO_BUTTONS_AVAILABLE:
|
||||||
|
logger.info("GPIO buttons configured but RPi.GPIO not available (not on Raspberry Pi)")
|
||||||
|
|
||||||
self._initialized = False
|
self._initialized = False
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
@ -190,6 +234,7 @@ class HardwareDisplayHAL(DisplayHAL):
|
|||||||
- Accelerometer (if enabled)
|
- Accelerometer (if enabled)
|
||||||
- RTC (if enabled)
|
- RTC (if enabled)
|
||||||
- Power monitor (if enabled)
|
- Power monitor (if enabled)
|
||||||
|
- GPIO buttons (if configured)
|
||||||
"""
|
"""
|
||||||
if self._initialized:
|
if self._initialized:
|
||||||
logger.warning("Hardware HAL already initialized")
|
logger.warning("Hardware HAL already initialized")
|
||||||
@ -197,6 +242,12 @@ class HardwareDisplayHAL(DisplayHAL):
|
|||||||
|
|
||||||
logger.info("Initializing hardware components...")
|
logger.info("Initializing hardware components...")
|
||||||
await self.hal.initialize()
|
await self.hal.initialize()
|
||||||
|
|
||||||
|
# Initialize GPIO buttons
|
||||||
|
if self.gpio_handler:
|
||||||
|
logger.info("Initializing GPIO buttons...")
|
||||||
|
await self.gpio_handler.initialize()
|
||||||
|
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
logger.info("Hardware HAL initialized successfully")
|
logger.info("Hardware HAL initialized successfully")
|
||||||
|
|
||||||
@ -206,6 +257,12 @@ class HardwareDisplayHAL(DisplayHAL):
|
|||||||
return
|
return
|
||||||
|
|
||||||
logger.info("Cleaning up hardware HAL")
|
logger.info("Cleaning up hardware HAL")
|
||||||
|
|
||||||
|
# Clean up GPIO buttons
|
||||||
|
if self.gpio_handler:
|
||||||
|
logger.info("Cleaning up GPIO buttons...")
|
||||||
|
await self.gpio_handler.cleanup()
|
||||||
|
|
||||||
await self.hal.cleanup()
|
await self.hal.cleanup()
|
||||||
self._initialized = False
|
self._initialized = False
|
||||||
logger.info("Hardware HAL cleaned up")
|
logger.info("Hardware HAL cleaned up")
|
||||||
@ -232,7 +289,7 @@ class HardwareDisplayHAL(DisplayHAL):
|
|||||||
|
|
||||||
async def get_touch_event(self) -> Optional[AppTouchEvent]:
|
async def get_touch_event(self) -> Optional[AppTouchEvent]:
|
||||||
"""
|
"""
|
||||||
Get the next touch event from hardware.
|
Get the next touch event from hardware (touch sensor or GPIO buttons).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
TouchEvent if available, None if no event
|
TouchEvent if available, None if no event
|
||||||
@ -242,11 +299,20 @@ class HardwareDisplayHAL(DisplayHAL):
|
|||||||
- LONG_PRESS: Hold (< 30px movement, >= 500ms)
|
- LONG_PRESS: Hold (< 30px movement, >= 500ms)
|
||||||
- SWIPE_*: Directional swipes (>= 30px movement)
|
- SWIPE_*: Directional swipes (>= 30px movement)
|
||||||
- PINCH_IN/OUT: Two-finger pinch gestures
|
- PINCH_IN/OUT: Two-finger pinch gestures
|
||||||
|
|
||||||
|
GPIO buttons are also polled and generate TouchEvent objects.
|
||||||
"""
|
"""
|
||||||
if not self._initialized:
|
if not self._initialized:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Get event from dreader-hal
|
# Check GPIO buttons first (they're more responsive)
|
||||||
|
if self.gpio_handler:
|
||||||
|
button_event = await self.gpio_handler.get_button_event()
|
||||||
|
if button_event:
|
||||||
|
logger.info(f"GPIO button event: {button_event.gesture.value}")
|
||||||
|
return button_event
|
||||||
|
|
||||||
|
# Get event from dreader-hal touch sensor
|
||||||
hal_event = await self.hal.get_touch_event()
|
hal_event = await self.hal.get_touch_event()
|
||||||
|
|
||||||
if hal_event is None:
|
if hal_event is None:
|
||||||
@ -564,13 +630,13 @@ class HardwareDisplayHAL(DisplayHAL):
|
|||||||
|
|
||||||
async def get_event(self) -> Optional[AppTouchEvent]:
|
async def get_event(self) -> Optional[AppTouchEvent]:
|
||||||
"""
|
"""
|
||||||
Get the next event from any input source (touch or accelerometer).
|
Get the next event from any input source (GPIO, touch, or accelerometer).
|
||||||
|
|
||||||
This is a convenience method that polls both touch and accelerometer
|
This is a convenience method that polls all input sources in a single call.
|
||||||
in a single call, prioritizing touch events over tilt events.
|
Priority order: GPIO buttons > touch sensor > accelerometer tilt
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
TouchEvent from either touch sensor or accelerometer, or None if no event
|
TouchEvent from GPIO, touch sensor, or accelerometer, or None if no event
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
while running:
|
while running:
|
||||||
@ -579,12 +645,28 @@ class HardwareDisplayHAL(DisplayHAL):
|
|||||||
handle_gesture(event)
|
handle_gesture(event)
|
||||||
await asyncio.sleep(0.01)
|
await asyncio.sleep(0.01)
|
||||||
"""
|
"""
|
||||||
# Check touch first (higher priority)
|
# Check GPIO buttons first (most responsive)
|
||||||
touch_event = await self.get_touch_event()
|
if self.gpio_handler:
|
||||||
if touch_event:
|
button_event = await self.gpio_handler.get_button_event()
|
||||||
return touch_event
|
if button_event:
|
||||||
|
logger.info(f"GPIO button event: {button_event.gesture.value}")
|
||||||
|
return button_event
|
||||||
|
|
||||||
# Check accelerometer tilt
|
# Check touch sensor (second priority)
|
||||||
|
# Get event from dreader-hal touch sensor directly
|
||||||
|
hal_event = await self.hal.get_touch_event()
|
||||||
|
if hal_event is not None:
|
||||||
|
# Convert from dreader-hal TouchEvent to application TouchEvent
|
||||||
|
app_gesture = GESTURE_TYPE_MAP.get(hal_event.gesture)
|
||||||
|
if app_gesture is not None:
|
||||||
|
logger.debug(f"Touch event: {app_gesture.value} at ({hal_event.x}, {hal_event.y})")
|
||||||
|
return AppTouchEvent(
|
||||||
|
gesture=app_gesture,
|
||||||
|
x=hal_event.x,
|
||||||
|
y=hal_event.y
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check accelerometer tilt (lowest priority)
|
||||||
if hasattr(self, 'accel_up_vector'):
|
if hasattr(self, 'accel_up_vector'):
|
||||||
tilt_event = await self.get_tilt_gesture()
|
tilt_event = await self.get_tilt_gesture()
|
||||||
if tilt_event:
|
if tilt_event:
|
||||||
|
|||||||
@ -10,6 +10,8 @@ Handles:
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Dict, Optional, Tuple
|
from typing import List, Dict, Optional, Tuple
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
@ -29,6 +31,8 @@ from pyWebLayout.core.query import QueryResult
|
|||||||
from .book_utils import scan_book_directory, extract_book_metadata
|
from .book_utils import scan_book_directory, extract_book_metadata
|
||||||
from .state import LibraryState
|
from .state import LibraryState
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class LibraryManager:
|
class LibraryManager:
|
||||||
"""
|
"""
|
||||||
@ -100,19 +104,34 @@ class LibraryManager:
|
|||||||
Returns:
|
Returns:
|
||||||
List of book dictionaries with metadata
|
List of book dictionaries with metadata
|
||||||
"""
|
"""
|
||||||
|
start_time = time.time()
|
||||||
|
logger.info(f"[LIBRARY] Scanning library: {self.library_path}")
|
||||||
print(f"Scanning library: {self.library_path}")
|
print(f"Scanning library: {self.library_path}")
|
||||||
|
|
||||||
if not self.library_path.exists():
|
if not self.library_path.exists():
|
||||||
|
logger.error(f"Library path does not exist: {self.library_path}")
|
||||||
print(f"Library path does not exist: {self.library_path}")
|
print(f"Library path does not exist: {self.library_path}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Scan directory
|
# Scan directory
|
||||||
|
scan_start = time.time()
|
||||||
self.books = scan_book_directory(self.library_path)
|
self.books = scan_book_directory(self.library_path)
|
||||||
|
scan_elapsed = time.time() - scan_start
|
||||||
|
logger.info(f"[LIBRARY] Directory scan completed in {scan_elapsed:.2f}s - found {len(self.books)} books")
|
||||||
|
|
||||||
# Cache covers to disk if not already cached
|
# Cache covers to disk if not already cached
|
||||||
for book in self.books:
|
cache_start = time.time()
|
||||||
|
for i, book in enumerate(self.books, 1):
|
||||||
|
book_start = time.time()
|
||||||
self._cache_book_cover(book)
|
self._cache_book_cover(book)
|
||||||
|
book_elapsed = time.time() - book_start
|
||||||
|
if book_elapsed > 0.1: # Only log if caching took significant time
|
||||||
|
logger.info(f"[LIBRARY] Cached cover {i}/{len(self.books)}: {book['title']} ({book_elapsed:.2f}s)")
|
||||||
|
cache_elapsed = time.time() - cache_start
|
||||||
|
logger.info(f"[LIBRARY] Cover caching completed in {cache_elapsed:.2f}s")
|
||||||
|
|
||||||
|
total_elapsed = time.time() - start_time
|
||||||
|
logger.info(f"[LIBRARY] Library scan complete: {len(self.books)} books in {total_elapsed:.2f}s")
|
||||||
print(f"Found {len(self.books)} books in library")
|
print(f"Found {len(self.books)} books in library")
|
||||||
return self.books
|
return self.books
|
||||||
|
|
||||||
@ -310,15 +329,20 @@ class LibraryManager:
|
|||||||
Returns:
|
Returns:
|
||||||
PIL Image of the rendered library
|
PIL Image of the rendered library
|
||||||
"""
|
"""
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
if table is None:
|
if table is None:
|
||||||
if self.library_table is None:
|
if self.library_table is None:
|
||||||
print("No table to render, creating one first...")
|
print("No table to render, creating one first...")
|
||||||
|
logger.info("[LIBRARY] Creating library table...")
|
||||||
self.create_library_table()
|
self.create_library_table()
|
||||||
table = self.library_table
|
table = self.library_table
|
||||||
|
|
||||||
print("Rendering library table...")
|
print("Rendering library table...")
|
||||||
|
logger.info("[LIBRARY] Rendering library table...")
|
||||||
|
|
||||||
# Create page
|
# Create page
|
||||||
|
page_start = time.time()
|
||||||
page_style = PageStyle(
|
page_style = PageStyle(
|
||||||
border_width=0,
|
border_width=0,
|
||||||
padding=(30, 30, 30, 30),
|
padding=(30, 30, 30, 30),
|
||||||
@ -328,6 +352,8 @@ class LibraryManager:
|
|||||||
page = Page(size=self.page_size, style=page_style)
|
page = Page(size=self.page_size, style=page_style)
|
||||||
canvas = page.render()
|
canvas = page.render()
|
||||||
draw = ImageDraw.Draw(canvas)
|
draw = ImageDraw.Draw(canvas)
|
||||||
|
page_elapsed = time.time() - page_start
|
||||||
|
logger.info(f"[LIBRARY] Page creation took {page_elapsed:.2f}s")
|
||||||
|
|
||||||
# Table style
|
# Table style
|
||||||
table_style = TableStyle(
|
table_style = TableStyle(
|
||||||
@ -344,6 +370,8 @@ class LibraryManager:
|
|||||||
table_width = page.size[0] - page_style.padding[1] - page_style.padding[3]
|
table_width = page.size[0] - page_style.padding[1] - page_style.padding[3]
|
||||||
|
|
||||||
# Render table with canvas support for images
|
# Render table with canvas support for images
|
||||||
|
render_start = time.time()
|
||||||
|
logger.info("[LIBRARY] Starting table render (this may load fonts)...")
|
||||||
self.table_renderer = TableRenderer(
|
self.table_renderer = TableRenderer(
|
||||||
table,
|
table,
|
||||||
table_origin,
|
table_origin,
|
||||||
@ -353,10 +381,15 @@ class LibraryManager:
|
|||||||
canvas # Pass canvas to enable image rendering
|
canvas # Pass canvas to enable image rendering
|
||||||
)
|
)
|
||||||
self.table_renderer.render()
|
self.table_renderer.render()
|
||||||
|
render_elapsed = time.time() - render_start
|
||||||
|
logger.info(f"[LIBRARY] Table rendering took {render_elapsed:.2f}s")
|
||||||
|
|
||||||
# Store rendered page for query support
|
# Store rendered page for query support
|
||||||
self.rendered_page = page
|
self.rendered_page = page
|
||||||
|
|
||||||
|
total_elapsed = time.time() - start_time
|
||||||
|
logger.info(f"[LIBRARY] Total render time: {total_elapsed:.2f}s")
|
||||||
|
|
||||||
return canvas
|
return canvas
|
||||||
|
|
||||||
def handle_library_tap(self, x: int, y: int) -> Optional[str]:
|
def handle_library_tap(self, x: int, y: int) -> Optional[str]:
|
||||||
|
|||||||
@ -433,8 +433,9 @@ class DReaderApplication:
|
|||||||
This method sends the current image to the HAL for display.
|
This method sends the current image to the HAL for display.
|
||||||
"""
|
"""
|
||||||
if self.current_image:
|
if self.current_image:
|
||||||
logger.debug(f"Updating display: {self.current_image.size}")
|
logger.info(f"[DISPLAY] Updating display: {self.current_image.size} in {self.state.mode.value} mode")
|
||||||
await self.display_hal.show_image(self.current_image)
|
await self.display_hal.show_image(self.current_image)
|
||||||
|
logger.info("[DISPLAY] Display update complete")
|
||||||
else:
|
else:
|
||||||
logger.warning("No image to display")
|
logger.warning("No image to display")
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user