diff --git a/apps/audiobookshelf/README.md b/apps/audiobookshelf/README.md new file mode 100644 index 000000000..8f601ea33 --- /dev/null +++ b/apps/audiobookshelf/README.md @@ -0,0 +1,28 @@ +# Audiobookshelf Now Playing + +Track currently playing audiobooks and podcasts from your [Audiobookshelf](https://www.audiobookshelf.org/) server on your Tronbyt / Tidbyt device. + +## Features + +- **Now Playing Display**: Shows current title, author, progress bar, and realistic time remaining (or elapsed / total). +- **Clean 1x & 2x Layouts**: + - **1x (64x32)**: Clean layout with title, author, progress bar, and dedicated right-aligned time remaining. + - **2x (128x64)**: High-resolution layout featuring chapter title, progress bar, percentage, and time remaining. +- **Configurable Listen Speed Factor**: Select your listening speed multiplier (`0.5x`, `0.75x`, `1.0x`, `1.25x`, `1.5x`, `1.75x`, `2.0x`, `2.5x`) to dynamically recalculate remaining listening time. +- **Aspect Ratio Detection**: Automatically detects square vs. rectangular book covers. +- **Stylized Fallback**: Procedurally renders a book cover if server covers are unavailable. +- **Customizable Appearance**: Multiple color themes, custom hex colors, progress bar styles, and scroll modes. + +## Configuration Options + +| Option | Type | Description | Default | +| --- | --- | --- | --- | +| `server_url` | Text | Host URL of your Audiobookshelf server | `http://localhost:13378` | +| `api_token` | Text (Secret) | User API token or Bearer token | — | +| `only_playing` | Toggle | Only display when an audiobook is actively playing | `false` | +| `listen_speed` | Dropdown | Listening speed multiplier (`0.5x` to `2.5x`) to recalculate realistic time remaining | `1.0` | +| `time_mode` | Dropdown | Time display format (`remaining`, `elapsed`, `total`) | `remaining` | +| `scroll_mode` | Dropdown | Title animation (`scroll`, `static`) | `scroll` | +| `bar_style` | Dropdown | Progress bar style (`standard`, `centered`) | `standard` | +| `color_scheme` | Dropdown | Accent color theme (ABS Gold, Teal, Green, etc., or Custom) | `gold` | +| `custom_color` | Text | Custom hex color code when `custom` color scheme is selected | `#f59e0b` | diff --git a/apps/audiobookshelf/audiobookshelf.star b/apps/audiobookshelf/audiobookshelf.star index 13e1b1f36..01125e592 100644 --- a/apps/audiobookshelf/audiobookshelf.star +++ b/apps/audiobookshelf/audiobookshelf.star @@ -68,6 +68,32 @@ def parse_hex_color(val, default = ABS_GOLD): return c return default +def parse_speed(val, default = 1.0): + if not val: + return default + s = str(val).strip().lower() + if s.endswith("x"): + s = s[:-1].strip() + if not s: + return default + has_digit = False + dot_count = 0 + for ch in s.elems(): + if ch == ".": + dot_count += 1 + if dot_count > 1: + return default + elif ch in "0123456789": + has_digit = True + else: + return default + if not has_digit: + return default + speed = float(s) + if speed <= 0.1 or speed > 10.0: + return default + return speed + def render_smart_text(text, font, color, max_width, scale, scroll_mode = "scroll"): """Renders text: static if scroll_mode is 'static' or text fits max_width, marquee if scrolling is enabled and overflows.""" if scroll_mode == "static": @@ -337,7 +363,7 @@ def render_cover_fallback(scale, width, height, title, accent_color = ABS_GOLD): ), ) -def render_abs_view(scale, width, data, cover_bytes, font_title, font_tiny, cover_aspect = "square", bar_style = "standard", accent_color = ABS_GOLD, time_mode = "remaining", scroll_mode = "scroll"): +def render_abs_view(scale, width, data, cover_bytes, font_title, font_tiny, cover_aspect = "square", bar_style = "standard", accent_color = ABS_GOLD, time_mode = "remaining", scroll_mode = "scroll", listen_speed = 1.0): if cover_aspect == "rectangular": cover_width = 24 * scale cover_height = 30 * scale @@ -358,7 +384,8 @@ def render_abs_view(scale, width, data, cover_bytes, font_title, font_tiny, cove progress_pct = int(data["progress"] * 100) # Format time display cleanly for compact width - time_left = data["duration"] - data["current_time"] + raw_time_left = data["duration"] - data["current_time"] + time_left = int(raw_time_left / listen_speed) if listen_speed > 0 else raw_time_left if time_mode == "elapsed": time_display = format_time(data["current_time"], compact = True) elif time_mode == "total": @@ -427,15 +454,25 @@ def render_abs_view(scale, width, data, cover_bytes, font_title, font_tiny, cove column_children.append(progress_bar) - bottom_row = render.Row( - expanded = True, - cross_align = "center", - main_align = "space_between", - children = [ - render.Text("%d%%" % progress_pct, font = font_tiny, color = accent_color), - render.Text(time_display, font = font_tiny, color = MUTED), - ], - ) + if scale == 1: + bottom_row = render.Row( + expanded = True, + cross_align = "center", + main_align = "end", + children = [ + render.Text(time_display, font = font_tiny, color = MUTED), + ], + ) + else: + bottom_row = render.Row( + expanded = True, + cross_align = "center", + main_align = "space_between", + children = [ + render.Text("%d%%" % progress_pct, font = font_tiny, color = accent_color), + render.Text(time_display, font = font_tiny, color = MUTED), + ], + ) column_children.append(bottom_row) text_column = render.Column( @@ -475,6 +512,9 @@ def main(config): time_mode = config.str("time_mode", "remaining") scroll_mode = config.str("scroll_mode", "scroll") + speed_setting = config.str("listen_speed", "") or config.str("read_speed", "") or config.str("playback_speed", "1.0") + listen_speed = parse_speed(speed_setting, default = 1.0) + if color_scheme == "custom": accent_color = parse_hex_color(config.str("custom_color", "#f59e0b"), ABS_GOLD) else: @@ -530,6 +570,7 @@ def main(config): accent_color = accent_color, time_mode = time_mode, scroll_mode = scroll_mode, + listen_speed = listen_speed, ) delay = 40 // scale @@ -626,5 +667,22 @@ def get_schema(): schema.Option(display = "Total Duration (3h56m)", value = "total"), ], ), + schema.Dropdown( + id = "listen_speed", + name = "Listen Speed", + desc = "Playback speed factor to recalculate realistic time remaining", + icon = "gauge", + default = "1.0", + options = [ + schema.Option(display = "0.5x", value = "0.5"), + schema.Option(display = "0.75x", value = "0.75"), + schema.Option(display = "1.0x (Normal)", value = "1.0"), + schema.Option(display = "1.25x", value = "1.25"), + schema.Option(display = "1.5x", value = "1.5"), + schema.Option(display = "1.75x", value = "1.75"), + schema.Option(display = "2.0x", value = "2.0"), + schema.Option(display = "2.5x", value = "2.5"), + ], + ), ], ) diff --git a/apps/audiobookshelf/audiobookshelf.webp b/apps/audiobookshelf/audiobookshelf.webp index 55b9dbb5f..45287b389 100644 Binary files a/apps/audiobookshelf/audiobookshelf.webp and b/apps/audiobookshelf/audiobookshelf.webp differ