From 727141198e0c7a843b0da943c77e3cf3352109b2 Mon Sep 17 00:00:00 2001 From: ChuckBuilds <33324927+ChuckBuilds@users.noreply.github.com> Date: Mon, 21 Apr 2025 21:51:11 -0500 Subject: [PATCH] Add YouTube display feature with channel stats and logo. Move sensitive credentials to secrets file. --- config/config.json | 7 +- config/config_secrets.template.json | 4 + src/youtube_display.py | 115 ++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/youtube_display.py diff --git a/config/config.json b/config/config.json index e100938f..cdcfdc66 100644 --- a/config/config.json +++ b/config/config.json @@ -38,7 +38,8 @@ "nba_live": 30, "nba_recent": 20, "nba_upcoming": 20, - "calendar": 30 + "calendar": 30, + "youtube": 20 } }, "clock": { @@ -109,5 +110,9 @@ "nba_upcoming": true }, "live_game_duration": 30 + }, + "youtube": { + "enabled": true, + "update_interval": 3600 } } \ No newline at end of file diff --git a/config/config_secrets.template.json b/config/config_secrets.template.json index 6153736f..0a454c14 100644 --- a/config/config_secrets.template.json +++ b/config/config_secrets.template.json @@ -1,5 +1,9 @@ { "weather": { "api_key": "YOUR_OPENWEATHERMAP_API_KEY" + }, + "youtube": { + "api_key": "YOUR_YOUTUBE_API_KEY", + "channel_id": "YOUR_YOUTUBE_CHANNEL_ID" } } \ No newline at end of file diff --git a/src/youtube_display.py b/src/youtube_display.py new file mode 100644 index 00000000..d9a799a9 --- /dev/null +++ b/src/youtube_display.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +import json +import time +from PIL import Image, ImageDraw, ImageFont +import requests +from rgbmatrix import RGBMatrix, RGBMatrixOptions +import os + +class YouTubeDisplay: + def __init__(self, config_path='config/config.json', secrets_path='config/config_secrets.json'): + self.config = self._load_config(config_path) + self.secrets = self._load_config(secrets_path) + self.matrix = self._setup_matrix() + self.canvas = self.matrix.CreateFrameCanvas() + self.font = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 8) + self.youtube_logo = Image.open("assets/youtube_logo.png") + + def _load_config(self, config_path): + with open(config_path, 'r') as f: + return json.load(f) + + def _setup_matrix(self): + options = RGBMatrixOptions() + display_config = self.config['display']['hardware'] + + options.rows = display_config['rows'] + options.cols = display_config['cols'] + options.chain_length = display_config['chain_length'] + options.parallel = display_config['parallel'] + options.hardware_mapping = display_config['hardware_mapping'] + options.brightness = display_config['brightness'] + options.pwm_bits = display_config['pwm_bits'] + options.pwm_lsb_nanoseconds = display_config['pwm_lsb_nanoseconds'] + options.disable_hardware_pulsing = display_config['disable_hardware_pulsing'] + options.show_refresh_rate = display_config['show_refresh_rate'] + options.limit_refresh_rate_hz = display_config['limit_refresh_rate_hz'] + options.gpio_slowdown = self.config['display']['runtime']['gpio_slowdown'] + + return RGBMatrix(options=options) + + def _get_channel_stats(self, channel_id): + api_key = self.secrets['youtube']['api_key'] + url = f"https://www.googleapis.com/youtube/v3/channels?part=statistics,snippet&id={channel_id}&key={api_key}" + + try: + response = requests.get(url) + data = response.json() + if data['items']: + channel = data['items'][0] + return { + 'title': channel['snippet']['title'], + 'subscribers': int(channel['statistics']['subscriberCount']), + 'views': int(channel['statistics']['viewCount']) + } + except Exception as e: + print(f"Error fetching YouTube stats: {e}") + return None + + def _create_display(self, channel_stats): + # Create a new image with the matrix dimensions + image = Image.new('RGB', (self.matrix.width, self.matrix.height)) + draw = ImageDraw.Draw(image) + + # Resize YouTube logo to fit + logo_height = self.matrix.height // 3 + logo_width = int(self.youtube_logo.width * (logo_height / self.youtube_logo.height)) + resized_logo = self.youtube_logo.resize((logo_width, logo_height)) + + # Calculate positions + logo_x = (self.matrix.width - logo_width) // 2 + logo_y = 0 + + # Paste the logo + image.paste(resized_logo, (logo_x, logo_y)) + + # Draw channel name + channel_name = channel_stats['title'] + name_bbox = draw.textbbox((0, 0), channel_name, font=self.font) + name_width = name_bbox[2] - name_bbox[0] + name_x = (self.matrix.width - name_width) // 2 + name_y = logo_height + 5 + draw.text((name_x, name_y), channel_name, font=self.font, fill=(255, 255, 255)) + + # Draw subscriber count + subs_text = f"{channel_stats['subscribers']:,} subscribers" + subs_bbox = draw.textbbox((0, 0), subs_text, font=self.font) + subs_width = subs_bbox[2] - subs_bbox[0] + subs_x = (self.matrix.width - subs_width) // 2 + subs_y = name_y + 15 + draw.text((subs_x, subs_y), subs_text, font=self.font, fill=(255, 255, 255)) + + return image + + def run(self): + if not self.config.get('youtube', {}).get('enabled', False): + return + + channel_id = self.secrets['youtube']['channel_id'] + duration = self.config['display']['display_durations']['youtube'] + channel_stats = self._get_channel_stats(channel_id) + + if channel_stats: + display_image = self._create_display(channel_stats) + self.canvas.SetImage(display_image) + self.matrix.SwapOnVSync(self.canvas) + time.sleep(duration) + + def cleanup(self): + self.matrix.Clear() + +if __name__ == "__main__": + # Example usage + youtube_display = YouTubeDisplay() + youtube_display.run() + youtube_display.cleanup() \ No newline at end of file