diff --git a/plugins.json b/plugins.json index 1fc5dee5..293a100f 100644 --- a/plugins.json +++ b/plugins.json @@ -478,10 +478,10 @@ "plugin_path": "plugins/ledmatrix-music", "stars": 0, "downloads": 0, - "last_updated": "2026-05-15", + "last_updated": "2026-07-09", "verified": true, "screenshot": "", - "latest_version": "1.0.6" + "latest_version": "1.0.8" }, { "id": "news", diff --git a/plugins/ledmatrix-music/LICENSE b/plugins/ledmatrix-music/LICENSE index b5a96de3..e653a0c1 100644 --- a/plugins/ledmatrix-music/LICENSE +++ b/plugins/ledmatrix-music/LICENSE @@ -1,21 +1,17 @@ -MIT License +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 -Copyright (c) 2025 ChuckBuilds +Copyright (C) 2025 LEDMatrix Team -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/plugins/ledmatrix-music/README.md b/plugins/ledmatrix-music/README.md index dc6073da..d0b08e7c 100644 --- a/plugins/ledmatrix-music/README.md +++ b/plugins/ledmatrix-music/README.md @@ -307,7 +307,7 @@ For issues, feature requests, or questions: ## License -This plugin is licensed under the MIT License. See the LICENSE file for details. +This plugin is licensed under the GNU General Public License v3.0. See the [LICENSE](LICENSE) file for details. ## Contributing diff --git a/plugins/ledmatrix-music/manager.py b/plugins/ledmatrix-music/manager.py index 2a7a2508..d0fa9a42 100644 --- a/plugins/ledmatrix-music/manager.py +++ b/plugins/ledmatrix-music/manager.py @@ -733,6 +733,51 @@ def update(self) -> None: if not self.poll_thread or not self.poll_thread.is_alive(): self.start_polling() + def _clip_text_to_width(self, text, font, max_width): + """Trim trailing characters so the rendered text fits within max_width px. + + The scrolling marquee rotates which characters lead, but the drawn + string is still full-length; without this clip it overflows the right + edge of the panel. This keeps the text inside its viewport, which + matters most on narrow 64px-wide displays where the text area is only + ~29px wide. + """ + if max_width <= 0: + # No horizontal room for text (e.g. a square panel where the album + # art fills the full width) — draw nothing rather than overflow. + return "" + if not text: + return text + if self.display_manager.get_text_width(text, font) <= max_width: + return text + clipped = text + while clipped and self.display_manager.get_text_width(clipped, font) > max_width: + clipped = clipped[:-1] + return clipped + + def _truncate_text_with_ellipsis(self, text, font, max_width): + """Truncate text to fit max_width px, appending "..." when trimmed. + + Used by the scrolling-disabled branches. Reserves room for the + ellipsis before clipping, so the result already fits the viewport and + survives the draw-time _clip_text_to_width pass unchanged — otherwise + that pass would trim the trailing "..." back off and drop the + truncation indicator. + """ + if max_width <= 0: + return "" + if not text: + return text + if self.display_manager.get_text_width(text, font) <= max_width: + return text + ellipsis = "..." + ellipsis_width = self.display_manager.get_text_width(ellipsis, font) + if ellipsis_width >= max_width: + # Not even room for the ellipsis — clip the raw text instead. + return self._clip_text_to_width(text, font, max_width) + clipped = self._clip_text_to_width(text, font, max_width - ellipsis_width) + return clipped + ellipsis + def display(self, force_clear: bool = False) -> None: """Display music information - called by plugin system.""" perform_full_refresh_this_cycle = force_clear @@ -825,10 +870,11 @@ def display(self, force_clear: bool = False) -> None: if perform_full_refresh_this_cycle or not self.is_currently_showing_nothing_playing: self.display_manager.clear() - text_width = self.display_manager.get_text_width("Nothing Playing", self.display_manager.regular_font) - x_pos = (self.display_manager.matrix.width - text_width) // 2 + np_text = self._clip_text_to_width("Nothing Playing", self.display_manager.regular_font, self.display_manager.matrix.width) + text_width = self.display_manager.get_text_width(np_text, self.display_manager.regular_font) + x_pos = max(0, (self.display_manager.matrix.width - text_width) // 2) y_pos = (self.display_manager.matrix.height // 2) - 4 - self.display_manager.draw_text("Nothing Playing", x=x_pos, y=y_pos, font=self.display_manager.regular_font) + self.display_manager.draw_text(np_text, x=x_pos, y=y_pos, font=self.display_manager.regular_font) self.display_manager.update_display() self.is_currently_showing_nothing_playing = True @@ -1044,8 +1090,8 @@ def _safe_y_percent(value, fallback): self.scroll_position_title = (self.scroll_position_title + 1) % len(title) self.title_scroll_tick = 0 elif title_width > text_area_width and not title_config['enabled']: - # Scrolling disabled - truncate text - current_title_display_text = title[:text_area_width // 6] + "..." # Rough truncation + # Scrolling disabled - truncate to the viewport with an ellipsis + current_title_display_text = self._truncate_text_with_ellipsis(title, font_title, text_area_width) self.scroll_position_title = 0 self.title_scroll_tick = 0 else: @@ -1056,7 +1102,7 @@ def _safe_y_percent(value, fallback): self.title_end_pause_counter = 0 self.title_at_end = False - self.display_manager.draw_text(current_title_display_text, + self.display_manager.draw_text(self._clip_text_to_width(current_title_display_text, font_title, text_area_width), x=text_area_x_start, y=y_pos_title_top, color=(255, 255, 255), font=font_title) # Artist scrolling with configurable settings @@ -1099,8 +1145,8 @@ def _safe_y_percent(value, fallback): self.scroll_position_artist = (self.scroll_position_artist + 1) % len(artist) self.artist_scroll_tick = 0 elif artist_width > text_area_width and not artist_config['enabled']: - # Scrolling disabled - truncate text - current_artist_display_text = artist[:text_area_width // 5] + "..." # Rough truncation + # Scrolling disabled - truncate to the viewport with an ellipsis + current_artist_display_text = self._truncate_text_with_ellipsis(artist, font_artist, text_area_width) self.scroll_position_artist = 0 self.artist_scroll_tick = 0 else: @@ -1111,7 +1157,7 @@ def _safe_y_percent(value, fallback): self.artist_end_pause_counter = 0 self.artist_at_end = False - self.display_manager.draw_text(current_artist_display_text, + self.display_manager.draw_text(self._clip_text_to_width(current_artist_display_text, font_artist, text_area_width), x=text_area_x_start, y=y_pos_artist_top, color=(180, 180, 180), font=font_artist) # Album @@ -1127,7 +1173,7 @@ def _safe_y_percent(value, fallback): if album_width <= text_area_width: # Album fits without scrolling - display normally self.logger.debug(f"MusicPlugin.display: Drawing album '{album}' at ({text_area_x_start}, {y_pos_album_top}) - fits without scrolling") - self.display_manager.draw_text(album, + self.display_manager.draw_text(self._clip_text_to_width(album, font_album, text_area_width), x=text_area_x_start, y=y_pos_album_top, color=(150, 150, 150), font=font_album) self.scroll_position_album = 0 self.album_scroll_tick = 0 @@ -1173,13 +1219,13 @@ def _safe_y_percent(value, fallback): self.scroll_position_album = (self.scroll_position_album + 1) % len(album) self.album_scroll_tick = 0 else: - # Scrolling disabled - truncate text - current_album_display_text = album[:text_area_width // 5] + "..." # Rough truncation + # Scrolling disabled - truncate to the viewport with an ellipsis + current_album_display_text = self._truncate_text_with_ellipsis(album, font_album, text_area_width) self.scroll_position_album = 0 self.album_scroll_tick = 0 self.logger.debug(f"MusicPlugin.display: Drawing scrolling album '{current_album_display_text}' at ({text_area_x_start}, {y_pos_album_top}) - position: {self.scroll_position_album}") - self.display_manager.draw_text(current_album_display_text, + self.display_manager.draw_text(self._clip_text_to_width(current_album_display_text, font_album, text_area_width), x=text_area_x_start, y=y_pos_album_top, color=(150, 150, 150), font=font_album) else: self.logger.debug(f"MusicPlugin.display: Album '{album}' not displayed - insufficient height (available: {available_height_for_album}, needed: {album_height})") @@ -1188,7 +1234,7 @@ def _safe_y_percent(value, fallback): duration_ms = current_track_info_snapshot.get('duration_ms', 0) progress_ms = current_track_info_snapshot.get('progress_ms', 0) - if duration_ms > 0: + if duration_ms > 0 and text_area_width > 0: bar_total_width = text_area_width filled_ratio = progress_ms / duration_ms filled_width = int(filled_ratio * bar_total_width) diff --git a/plugins/ledmatrix-music/manifest.json b/plugins/ledmatrix-music/manifest.json index 19a753cb..f494ec78 100644 --- a/plugins/ledmatrix-music/manifest.json +++ b/plugins/ledmatrix-music/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-music", "name": "Music Player - Now Playing", - "version": "1.0.6", + "version": "1.0.8", "description": "Real-time now playing display for Spotify and YouTube Music with album art, scrolling text, and progress bars", "author": "ChuckBuilds", "entry_point": "manager.py", @@ -28,7 +28,7 @@ ], "homepage": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/ledmatrix-music", "website": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/ledmatrix-music", - "license": "MIT", + "license": "GPL-3.0", "compatible_versions": [ ">=2.0.0" ], @@ -66,10 +66,22 @@ } ], "versions": [ + { + "released": "2026-07-09", + "version": "1.0.8", + "notes": "When text scrolling is disabled, truncate the title/artist/album to the text-area width and append an ellipsis, reserving room for it so the '...' indicator isn't clipped away at draw time.", + "ledmatrix_min_version": "2.0.0" + }, + { + "released": "2026-07-08", + "version": "1.0.7", + "notes": "Fix now-playing text overflowing the right edge on narrow (64px-wide) panels: clip the scrolling title/artist/album text to the text-area viewport so long text stays within bounds while it scrolls, instead of drawing past the panel edge. Also set the license to GPL-3.0 (project standard).", + "ledmatrix_min_version": "2.0.0" + }, { "released": "2026-05-15", "version": "1.0.6", - "ledmatrix_min": "2.0.0" + "ledmatrix_min_version": "2.0.0" }, { "version": "1.0.5", @@ -92,7 +104,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-05-15", + "last_updated": "2026-07-09", "stars": 0, "downloads": 0, "verified": true,