├── Available Plugins ├── 1WifiStatus Icons │ ├── Attributions.md │ ├── wifi-full.png │ ├── wifi-low.png │ ├── wifi-medium.png │ └── wifi-semifull.png ├── 1WifiStatus.py ├── COVID-Trends-Icons │ ├── Arrow-Down.png │ ├── Arrow-Up.png │ ├── Arrow-Zero.png │ └── Attribution.txt ├── COVIDTrends.py ├── Chromecast Icons │ ├── Attributions.md │ ├── Chromecast.png │ ├── gHome.png │ ├── pause_icon.png │ └── play_icon.png ├── ChromecastList.py ├── Forecast-Icons │ └── placed here to stop git from deleting it bad git ├── Forecast.py ├── LibraryGoBrr.py ├── Nekos.py ├── NewCovidCasesMLHU.py ├── NewCovidCasesOntario.py ├── NmapDevices.py ├── OutlookCalendar.py ├── Philips Hue.py ├── Philips Hue │ └── light.png ├── SCPPhoto.py ├── SMB Storage.py ├── Spotify.py ├── SteamFriends.py ├── UnifiClients.py ├── Weather.py ├── WemoKasaPlugs.py ├── WemoKasaPlugs │ ├── Attributions.md │ └── outlet.png └── YouTubeSubs.py ├── Card.py ├── Cards └── placed here to stop git from deleting it bad git ├── Colors.txt ├── ErrorLogger.py ├── Fonts └── font1.ttf ├── GenerateImage.py ├── Plugin Templates ├── 1UnitImage.py ├── FullCustomTemplate.py ├── ListOfObjects.py ├── MediaPlayer.py ├── ObjectGroup.py ├── SingleNumber.py ├── SingleRadialProgress.py └── SmallText.py ├── Plugins └── placed here to stop git from deleting it bad git ├── README.MD ├── SmartFrame.py └── requirements.txt /Available Plugins/1WifiStatus Icons/Attributions.md: -------------------------------------------------------------------------------- 1 | Base icon (wifi-full) is from Android Design icons. 2 | other variants are of the same icon but edited in Photoshop by RaddedMC. -------------------------------------------------------------------------------- /Available Plugins/1WifiStatus Icons/wifi-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/1WifiStatus Icons/wifi-full.png -------------------------------------------------------------------------------- /Available Plugins/1WifiStatus Icons/wifi-low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/1WifiStatus Icons/wifi-low.png -------------------------------------------------------------------------------- /Available Plugins/1WifiStatus Icons/wifi-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/1WifiStatus Icons/wifi-medium.png -------------------------------------------------------------------------------- /Available Plugins/1WifiStatus Icons/wifi-semifull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/1WifiStatus Icons/wifi-semifull.png -------------------------------------------------------------------------------- /Available Plugins/1WifiStatus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's Wifi Status icon for SmartFrame -- 1WifiStatus.py 3 | # This is a simple icon that gets wifi signal status. 4 | 5 | # Make sure you keep the 1WifiStatus folder in the same path as the plugin! 6 | # Required deps: Pillow, termcolor, pywifi, comtypes, access_points 7 | 8 | # No setup is required other than installing dependencies. 9 | 10 | sourcename = "Wifi Status" 11 | 12 | from PIL import Image, ImageFont, ImageDraw 13 | import os 14 | import sys 15 | import pywifi 16 | import access_points 17 | from pywifi import const 18 | 19 | SMARTFRAMEFOLDER = "" 20 | COLORS = [] 21 | 22 | interfaceoverride = 0 # If your device is weird and wifi isn't the first interface listed 23 | 24 | #### YOUR CODE HERE #### 25 | def GetCardData(): 26 | imagePath = "1WifiStatus Icons" # File within same folder as plugin (I recommend creating a subfolder with assets related to your plugin 27 | 28 | background = (50,50,100) # Red, Green, Blue 29 | alttext = "Whatever you want!" 30 | 31 | wifi = pywifi.PyWiFi() 32 | iface = wifi.interfaces()[interfaceoverride] 33 | if iface.status() is not const.IFACE_CONNECTED: 34 | # Wifi is not connected 35 | printC("No wifi! Either you're using ethernet or are offline.", "red") 36 | return None, None, None 37 | else: 38 | wifiScanner = access_points.get_scanner() 39 | try: 40 | wifiStrength = wifiScanner.get_access_points()[0].quality 41 | except: 42 | printC("Unknown error! Check the traceback!", "red") 43 | import traceback 44 | traceback.print_exc() 45 | exit() 46 | 47 | if wifiStrength >= 0 and wifiStrength <= 25: 48 | # LOW 49 | imagePath += "/wifi-low.png" 50 | alttext = "Wifi Strength is low!" 51 | printC(alttext) 52 | elif wifiStrength > 25 and wifiStrength <= 50: 53 | # MEDIUM 54 | imagePath += "/wifi-medium.png" 55 | alttext = "Wifi Strength is medium!" 56 | printC(alttext) 57 | elif wifiStrength > 50 and wifiStrength <= 75: 58 | # HIGH 59 | imagePath += "/wifi-semifull.png" 60 | alttext = "Wifi Strength is high!" 61 | printC(alttext) 62 | else: 63 | # HIGHEST 64 | imagePath += "/wifi-full.png" 65 | alttext = "Wifi Strength is highest!" 66 | printC(alttext) 67 | 68 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 69 | index = file.rfind("/") 70 | file = file[:index] 71 | fullImagePath = file + "/" + imagePath # File location of image 72 | 73 | return fullImagePath, background, alttext 74 | #### YOUR CODE HERE #### 75 | 76 | def GenerateCard(): 77 | tilesX = 1 # Change this to change tile size 78 | tilesY = 1 # Change this to change tile size 79 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 80 | imageresx = tilesX*dpifactor 81 | imageresy = tilesY*dpifactor 82 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 83 | imagedraw = ImageDraw.Draw(image) 84 | 85 | imageFile, background, alttext = GetCardData() 86 | 87 | if imageFile and background and alttext: 88 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=background) 89 | icon = Image.open(imageFile) 90 | icon = icon.resize((round(imageresx-(dpifactor/12)), round(imageresy-(dpifactor/12)))) 91 | try: 92 | image.paste(icon, (round(dpifactor/25), round(dpifactor/25)), mask=icon) 93 | except: 94 | printC("Error with transparency! Trying without...", "red") 95 | try: 96 | image.paste(icon, (round(dpifactor/25), round(dpifactor/25))) 97 | except: 98 | import traceback 99 | logError("Unable to display image! Check the traceback.", traceback.format_exc(), sourcename) 100 | return None, None, None, None 101 | else: 102 | printC("No data! Sending null data.", "red") 103 | return None, None, None, None 104 | 105 | return image, alttext, tilesX, tilesY 106 | 107 | 108 | def printC(string, color = "white"): 109 | from termcolor import colored 110 | print(sourcename + " | " + colored(str(string), color)) 111 | 112 | def GetPresets(): 113 | # Set presets 114 | printC("Getting deps...", "blue") 115 | currentLocation = os.getcwd().replace('\\', '/') 116 | nextindex = currentLocation.rfind("/") 117 | global SMARTFRAMEFOLDER 118 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 119 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 120 | else: 121 | SMARTFRAMEFOLDER = currentLocation 122 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 123 | 124 | sys.path.append(SMARTFRAMEFOLDER) 125 | 126 | printC("Gathering colors...", "blue") 127 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 128 | colorfileLines = colorfile.readlines() 129 | global COLORS 130 | for line in colorfileLines: 131 | if "#" in line: 132 | break 133 | else: 134 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 135 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 136 | 137 | GetPresets() 138 | from ErrorLogger import logError 139 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 140 | def GetCard(): 141 | 142 | # Generate card... 143 | printC("Starting card generation...", "blue") 144 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 145 | 146 | # Check if card exists 147 | if image and alttext and tilesX and tilesY: 148 | printC("Finished generating card!...", "green") 149 | 150 | 151 | # Setup output location 152 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 153 | printC("Will output to " + outputLocation, "cyan") 154 | 155 | # Save 156 | image.save(outputLocation) 157 | printC("Image saved to " + outputLocation + "!", "green") 158 | 159 | from Card import Card 160 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 161 | else: 162 | # No cards 163 | printC("No cards to return!...", "red") 164 | return None 165 | 166 | if __name__ == "__main__": 167 | GetCard() 168 | -------------------------------------------------------------------------------- /Available Plugins/COVID-Trends-Icons/Arrow-Down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/COVID-Trends-Icons/Arrow-Down.png -------------------------------------------------------------------------------- /Available Plugins/COVID-Trends-Icons/Arrow-Up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/COVID-Trends-Icons/Arrow-Up.png -------------------------------------------------------------------------------- /Available Plugins/COVID-Trends-Icons/Arrow-Zero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/COVID-Trends-Icons/Arrow-Zero.png -------------------------------------------------------------------------------- /Available Plugins/COVID-Trends-Icons/Attribution.txt: -------------------------------------------------------------------------------- 1 | Icon designs by RaddedMC! -------------------------------------------------------------------------------- /Available Plugins/COVIDTrends.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- COVIDTrends.py by @Raminh05 3 | # This is a plugin to display Rate of Change Increases/Decreases for COVID cases in the MLHU + The Province of Ontario 4 | # COVID Data from the Government of Ontario / The Provincial Ministry of Health 5 | # Time interval option lets plugin show up every 10 minutes 6 | 7 | # THIS PLUGIN REQUIRES BOTH 8 | # Required deps: Pillow, termcolor, csv, datetime, wget (deps for this plugin) 9 | # Required files: The COVID-Trends-Icons folder's content 10 | 11 | sourcename = "COVIDTrends" 12 | time_interval_option = False # true or false 13 | 14 | 15 | from PIL import Image, ImageFont, ImageDraw 16 | import os 17 | import sys 18 | import math 19 | from csv import reader 20 | from datetime import datetime 21 | import wget 22 | 23 | SMARTFRAMEFOLDER = "" 24 | COLORS = [] 25 | 26 | ### YOUR CODE HERE ### 27 | def GetCardData(): 28 | now = datetime.now() 29 | date = now.strftime("%Y-%m-%d") 30 | minute = int(now.strftime("%M")) 31 | 32 | # -- Time interval logic -- # 33 | global time_interval_option 34 | 35 | minute_list = [00, 10, 20, 30, 40, 50] 36 | 37 | if time_interval_option: 38 | if minute in minute_list: 39 | pass 40 | else: 41 | printC("Not the time yet!") 42 | exit() 43 | else: 44 | pass 45 | 46 | def GetPathWithinNeighbouringFolder(fileWithin, folder): 47 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 48 | index = file.rfind("/") 49 | file = file[:index] 50 | fullImagePath = file + "/" + folder + "/" + fileWithin # File location of image 51 | return fullImagePath 52 | 53 | # -- Calculate COVID trend -- # 54 | def trend_calculator(): 55 | 56 | london_cases = [] 57 | ontario_cases = [] 58 | 59 | # Parses and returns dates 60 | def dates(): 61 | f = open(SMARTFRAMEFOLDER + "/ontario_covid.csv", 'r') 62 | csv_date = list(reader(f))[-1][0] 63 | return csv_date 64 | 65 | # -- Date and csv currency logic (re-written by @RaddedMC 08/13/2021) -- # 66 | 67 | # Check if CSV Exists 68 | # If yes, run date check 69 | # If no, download new one 70 | 71 | # If date check successful, continue 72 | # If date check failed, download new file (unless already downloaded) 73 | 74 | def downloadFile(): 75 | try: 76 | wget.download('https://data.ontario.ca/dataset/f4f86e54-872d-43f8-8a86-3892fd3cb5e6/resource/8a88fe6d-d8fb-41a3-9d04-f0550a44999f/download/daily_change_in_cases_by_phu.csv', SMARTFRAMEFOLDER + "/ontario_covid.csv") 77 | except: 78 | import traceback 79 | logError("Unable to download COVID information! Check the traceback.", traceback.format_exc(), sourcename) 80 | raise ConnectionError 81 | 82 | printC("Checking for a CSV file...", "blue") 83 | if os.path.isfile(SMARTFRAMEFOLDER + "/ontario_covid.csv"): 84 | printC("Found a file!", "green") 85 | csv_date = dates() 86 | print(csv_date) 87 | if csv_date != date: 88 | printC("File is out of date. Downloading a new one.", "red") 89 | try: 90 | printC("Deleteing old csv file...", "yellow") 91 | os.remove("ontario_covid.csv") 92 | downloadFile() 93 | except ConnectionError: 94 | return None, None 95 | else: 96 | printC("File is up to date!", "green") 97 | else: 98 | printC("File doesn't exist! Downloading a new one.", "red") 99 | try: 100 | downloadFile() 101 | except ConnectionError: 102 | return None, None 103 | 104 | printC("Grabbing COVID data from file...", "blue") 105 | with open(SMARTFRAMEFOLDER + "/ontario_covid.csv", 'r') as f: 106 | data = list(reader(f)) 107 | london_cases = [i[16] for i in data[400::]] # London Case List 108 | ontario_cases = [i[35] for i in data[400::]] # Ontario Case List 109 | f.close() 110 | 111 | # -- Differences -- # 112 | london_diff = int(london_cases[-1]) - int(london_cases[-2]) # Today and Yesterday 113 | ontario_diff = int(ontario_cases[-1]) - int(ontario_cases[-2]) # Today and Yesterday 114 | 115 | london_today = london_cases[-1] + " new cases" # New cases 116 | ontario_today = ontario_cases[-1] + " new cases" # New cases 117 | 118 | if london_diff > 0: 119 | london_diff = "+" + str(london_diff) + "\nLondon\n" + london_today 120 | elif london_diff < 0: 121 | london_diff = str(london_diff) + "\nLondon\n" + london_today 122 | elif london_diff == 0: 123 | london_diff = "0\nLondon\n" + london_today 124 | 125 | if ontario_diff > 0: 126 | ontario_diff = "+" + str(ontario_diff) + "\nOntario\n" + ontario_today 127 | elif ontario_diff < 0: 128 | ontario_diff = str(ontario_diff) + "\nOntario\n" + ontario_today 129 | elif ontario_diff == 0: 130 | ontario_diff = "*No change\nOntario\n" + ontario_today 131 | 132 | return london_diff, ontario_diff 133 | 134 | trends = trend_calculator() 135 | if trends == (None, None): 136 | return None, None, None 137 | 138 | groupList = [] 139 | alttext = "" 140 | 141 | for trend in trends: 142 | if "-" in trend: 143 | groupList.append(Group(trend, [Item("Down arrow", GetPathWithinNeighbouringFolder("Arrow-Down.png", "COVID-Trends-Icons"), (128,162,240), bgFillAmt=1.0)])) 144 | alttext += (trend.replace("\n", " ") + " ") 145 | elif "+" in trend: 146 | groupList.append(Group(trend, [Item("Up arrow", GetPathWithinNeighbouringFolder("Arrow-Up.png", "COVID-Trends-Icons"), (240,128,128), bgFillAmt=1.0)])) 147 | alttext += (trend.replace("\n", " ") + " ") 148 | elif "0" in trend: 149 | groupList.append(Group(trend, [Item("Zero arrow", GetPathWithinNeighbouringFolder("Arrow-Zero.png", "COVID-Trends-Icons"), (192,192,192), bgFillAmt=1.0)])) # No change in trend 150 | alttext += (trend.replace("\n", " ") + " ") 151 | 152 | maintext = "COVID Trends" 153 | 154 | return groupList, maintext, alttext 155 | 156 | def GenerateCard(): 157 | 158 | # Get data 159 | groupList, maintext, alttext = GetCardData() 160 | 161 | # If data is present 162 | if groupList: 163 | # Calculate card height 164 | tilesX = 4 165 | tilesY = 1 166 | 167 | # Stuff 168 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 169 | imageresx = tilesX*dpifactor 170 | imageresy = tilesY*dpifactor 171 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 172 | imagedraw = ImageDraw.Draw(image) 173 | backgroundcolor = COLORS[3] # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 174 | maintextcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 175 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 20*round(dpifactor/50)) 176 | 177 | 178 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 179 | padding = dpifactor/50 180 | left = padding 181 | for group in groupList: 182 | printC("Getting Image for group " + group.groupName) 183 | try: 184 | groupImg = group.Image((round((imageresx/2)-(padding*2)),round(imageresy-(padding*4))), dpifactor/10) 185 | except: 186 | import traceback 187 | logError("Unknown error with group " + group.groupName + "! Moving on to next group...", traceback.format_exc(), sourcename) 188 | continue 189 | image.paste(groupImg, (round(left), round(padding*2)), mask = groupImg) 190 | left += imageresx/2 191 | else: 192 | printC("No data! Sending null data.", "red") 193 | return None, None, None, None 194 | 195 | return image, alttext, tilesX, tilesY 196 | 197 | # Item class in ObjectGroup 198 | class Item: 199 | itemName = "" 200 | iconLocation = "" # Icon file path 201 | bgColor = (0,0,0) # Background color of item 202 | bgFillAmt = 1 # Percentage (0 - 1) of background filledness, useful to show smart light brightness 203 | 204 | def __init__(self, itemName, iconLocation, bgColor, bgFillAmt=1): 205 | self.itemName = itemName 206 | self.iconLocation = iconLocation 207 | self.bgColor = bgColor 208 | self.bgFillAmt = bgFillAmt 209 | print("New Item | " + self.itemName + " with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor)) 210 | 211 | # Returns a pretty Item with a rounded-rectangle background 212 | def Image(self, xyres, cornerrad): 213 | 214 | # Create the image of provided size 215 | image = Image.new(mode="RGBA", size = (xyres, xyres)) 216 | imagedraw = ImageDraw.Draw(image) 217 | 218 | # This background changes height based on the fill amount. Useful for smart light brightness or speaker volume. 219 | imagedraw.rounded_rectangle([(0,0),(xyres,xyres)], fill=(255,255,255,100), radius=cornerrad) # BG 220 | imagedraw.rounded_rectangle([(0,round(xyres*(1-self.bgFillAmt))),(xyres,xyres)], fill=(round(self.bgColor[0]), round(self.bgColor[1]), round(self.bgColor[2])), radius=round(cornerrad)) # FG 221 | 222 | # Overlay the icon 223 | icon = Image.open(self.iconLocation) 224 | icon = icon.resize((round(xyres-(xyres/12)), round(xyres-(xyres/12)))) 225 | image.paste(icon, (round(xyres/25), round(xyres/25)), mask=icon) 226 | 227 | return image 228 | 229 | def __str__(self): 230 | return "Item object: " + self.itemName + "' with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor) 231 | 232 | # Group class in ObjectGroup 233 | class Group: 234 | groupName = "" 235 | itemArray = [] 236 | 237 | def __init__(self, groupName, itemArray): 238 | self.groupName = groupName 239 | self.itemArray = itemArray 240 | 241 | nameList = "" 242 | for item in self.itemArray: 243 | nameList += item.itemName 244 | nameList += ", " 245 | print("New ItemGroup | " + self.groupName + " with items " + nameList) 246 | 247 | def Image(self, xyres, cornerrad): 248 | # Create image of provided size 249 | image = Image.new(mode="RGBA", size = xyres) 250 | imagedraw = ImageDraw.Draw(image) 251 | 252 | # Create background 253 | imagedraw.rounded_rectangle([(0,0),xyres], fill=(0,0,0, 100), radius=cornerrad) 254 | 255 | # Overlay Items 256 | padding = round(((2*xyres[0]/3)/6)/10) 257 | imageWidth = round(((3.5*xyres[0]/3)/6)-(padding*2)) 258 | mainfontscalefactor = 40 / (self.groupName.count("\n")+1) 259 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", round(mainfontscalefactor*padding)) 260 | secondaryfontscalefactor = mainfontscalefactor / 2 261 | secondarytextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", round(secondaryfontscalefactor*padding)) 262 | splitdex = self.groupName.find("\n") 263 | imagedraw.text((padding*3, padding*2), self.groupName[:splitdex], font=maintextfont, fill=COLORS[1]) 264 | imagedraw.text((padding*3, mainfontscalefactor*padding), self.groupName[splitdex:], font=secondarytextfont, fill=COLORS[1]) 265 | leftmost = xyres[0]-imageWidth-(padding*2) 266 | 267 | item = self.itemArray[0] 268 | try: 269 | itemImage = item.Image(imageWidth, cornerrad) 270 | except: 271 | import traceback 272 | logError("Unknown error with image " + image.imageName + "! Moving on to next image...", traceback.format_exc(), sourcename) 273 | image.paste(itemImage, (round(leftmost), round(xyres[1]/3)), mask=itemImage) 274 | 275 | return image 276 | 277 | def __str__(self): 278 | nameList = "" 279 | for item in self.itemArray: 280 | nameList += item.itemName 281 | nameList += ", " 282 | print("ItemGroup: " + self.groupName + " with items " + nameList) 283 | 284 | def printC(string, color = "white"): 285 | from termcolor import colored 286 | print(sourcename + " | " + colored(str(string), color)) 287 | 288 | def GetPresets(): 289 | # Set presets 290 | printC("Getting deps...", "blue") 291 | currentLocation = os.getcwd().replace('\\', '/') 292 | nextindex = currentLocation.rfind("/") 293 | global SMARTFRAMEFOLDER 294 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 295 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 296 | else: 297 | SMARTFRAMEFOLDER = currentLocation 298 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 299 | 300 | sys.path.append(SMARTFRAMEFOLDER) 301 | 302 | printC("Gathering colors...", "blue") 303 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 304 | colorfileLines = colorfile.readlines() 305 | global COLORS 306 | for line in colorfileLines: 307 | if "#" in line: 308 | break 309 | else: 310 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 311 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 312 | 313 | GetPresets() 314 | from ErrorLogger import logError 315 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 316 | def GetCard(): 317 | 318 | # Generate card... 319 | printC("Starting card generation...", "blue") 320 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 321 | 322 | # Check if card exists 323 | if image and alttext and tilesX and tilesY: 324 | printC("Finished generating card!...", "green") 325 | 326 | 327 | # Setup output location 328 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 329 | printC("Will output to " + outputLocation, "cyan") 330 | 331 | # Save 332 | image.save(outputLocation) 333 | printC("Image saved to " + outputLocation + "!", "green") 334 | 335 | from Card import Card 336 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 337 | else: 338 | # No cards 339 | printC("No cards to return!...", "red") 340 | return None 341 | 342 | if __name__ == "__main__": 343 | GetCard() 344 | 345 | -------------------------------------------------------------------------------- /Available Plugins/Chromecast Icons/Attributions.md: -------------------------------------------------------------------------------- 1 | Play and pause icons are from Android Design icons. 2 | -------------------------------------------------------------------------------- /Available Plugins/Chromecast Icons/Chromecast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/Chromecast Icons/Chromecast.png -------------------------------------------------------------------------------- /Available Plugins/Chromecast Icons/gHome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/Chromecast Icons/gHome.png -------------------------------------------------------------------------------- /Available Plugins/Chromecast Icons/pause_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/Chromecast Icons/pause_icon.png -------------------------------------------------------------------------------- /Available Plugins/Chromecast Icons/play_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/Chromecast Icons/play_icon.png -------------------------------------------------------------------------------- /Available Plugins/Forecast-Icons/placed here to stop git from deleting it bad git: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/Forecast-Icons/placed here to stop git from deleting it bad git -------------------------------------------------------------------------------- /Available Plugins/Forecast.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- Forecast.py by @Raminh05 3 | # This is a plugin to display weather forecast for your specified location! 4 | 5 | # -- Bugs so Far: -- # 6 | # THE API IN USE REQUIRES GEOCODING! EXPECT LESS-KNOWN LOCATIONS TO HAVE INCORRECT DATA! 7 | # Icon fetching is super inefficient 8 | 9 | # Required deps: Pillow, termcolor, wget, requests, json, datetime, calendar 10 | # Required files: Paste the Forecast-Icons folder in /Plugins (or else the plugin would crash) 11 | 12 | # -- User-definable variables -- # 13 | sourcename = "Forecast" 14 | OWM_api_key = "b4f6dd2094bdd5048ce9025a901553df" 15 | mapbox_api_key = "pk.eyJ1IjoiY2Fubm9saSIsImEiOiJja21udzZpN3AxeXJmMm9zN3BuZGR3aTE0In0.w62dorEJ-QKwtJSswhRVaQ" 16 | base_url_geocode = "https://api.mapbox.com/geocoding/v5/mapbox.places/" 17 | city_name = "" # specify here! 18 | country = "" # specify here! 19 | unit = "" # specify here! 20 | 21 | # -- Initial module imports -- # 22 | from PIL import Image, ImageFont, ImageDraw 23 | import os 24 | import sys 25 | import math 26 | import requests, json # Fetching and parsing API responses 27 | import traceback # For error-logging 28 | 29 | SMARTFRAMEFOLDER = "" 30 | COLORS = [] 31 | 32 | ### YOUR CODE HERE ### 33 | def GetCardData(): 34 | def GetPathWithinNeighbouringFolder(fileWithin, folder): 35 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 36 | index = file.rfind("/") 37 | file = file[:index] 38 | fullImagePath = file + "/" + folder + "/" + fileWithin # File location of image 39 | return fullImagePath 40 | 41 | global unit 42 | 43 | # -- Checks if user entered a valid unit system -- # 44 | printC("Checking unit system...", "yellow") 45 | if unit == "metric": 46 | printC("Metric detected!", "green") 47 | symbol = 'C' # Sets C has char for Celsius 48 | pass 49 | 50 | elif unit == "imperial": 51 | printC("Imperial detected!", "green") 52 | symbol = 'F' # Sets F has char for Fahrenheit 53 | pass 54 | else: 55 | printC("Entered unit system is either non-existent or is invalid! Falling back to metric.", "yellow") 56 | unit = "metric" 57 | symbol = 'C' 58 | 59 | groupList = [] 60 | 61 | maintext = "Forecast" 62 | alttext = "Whatever you want!" 63 | 64 | # -- Mapbox URL -- # 65 | complete_url_geo = base_url_geocode + city_name + ".json?country=" + country + "&access_token=" + mapbox_api_key 66 | 67 | # -- Requests geocoding information -- # 68 | geo = requests.get(complete_url_geo) 69 | x_geo = geo.json() 70 | 71 | # -- Attempts to parse geocoding response -- # 72 | try: 73 | printC("Attempting to parse geocoding response...", "yellow") 74 | main_geo_info = x_geo["features"] 75 | main_cords = main_geo_info[0]["center"] 76 | longitude = main_cords[0] 77 | latitude = main_cords[1] 78 | place_name = main_geo_info[0]["place_name"] 79 | printC("Sucessfully parsed geocoding response!", "green") 80 | except: 81 | logError("MapBox API failed to find your specified location. Exiting process...", "", sourcename) 82 | print(complete_url_geo) # Debugging purposes 83 | exit() # Quits process 84 | 85 | # -- Assembles url to request weather data -- # 86 | forecast_data_url = "https://api.openweathermap.org/data/2.5/onecall?lat=" + str(latitude) + "&lon=" + str(longitude) + "&exclude=hourly,minutely,current,alerts&units=" + unit + "&appid=" + OWM_api_key 87 | 88 | # print(forecast_data_url) # Debugging purposes 89 | 90 | response = requests.get(forecast_data_url) 91 | x = response.json() 92 | forecast = x["daily"] 93 | 94 | # -- Second batch of module imports -- # 95 | from datetime import datetime # datetime parsing 96 | import calendar # For date to weekday conversion 97 | import wget # icon fetching 98 | 99 | # -- For loop to assemble items and groups -- # 100 | try: 101 | printC("Attempting to parse forecast response...", "yellow") 102 | for day in forecast: 103 | itemList = [] # Resets itemList for every day 104 | 105 | # -- Date variables and conversion -- # 106 | unix_time = float(day['dt']) 107 | datetime_date = datetime.utcfromtimestamp(unix_time) # Converts Unix Timestamp to datetime format 108 | strdate = datetime_date.strftime('%Y-%m-%d') # Converts datetime value to string 109 | 110 | # -- Weather variables -- # 111 | max_temp = str(round(day['temp']['max'])) + chr(176) + symbol # uses char set above for unit 112 | min_temp = str(round(day['temp']['min'])) + chr(176) + symbol 113 | condition = day['weather'][0]['description'].title() 114 | icon_url = "http://openweathermap.org/img/wn/" + day['weather'][0]['icon'] + "@2x.png" 115 | 116 | # -- Fetches icon -- # 117 | wget.download(icon_url, GetPathWithinNeighbouringFolder(day['weather'][0]['icon'] + "@2x.png", "Forecast-Icons")) 118 | 119 | # -- Date to day of the week conversion -- # 120 | day_of_the_week = calendar.day_name[datetime_date.weekday()] 121 | 122 | # -- Writes itemList and appends GroupList -- # 123 | itemList.append(Item(condition, GetPathWithinNeighbouringFolder(day['weather'][0]['icon'] + "@2x.png", "Forecast-Icons"), (255,255,255), bgFillAmt=1)) # Colour TBD 124 | groupList.append(Group(day_of_the_week + " " + strdate + "\n" + "High: " + max_temp + " | Low: " + min_temp , itemList)) 125 | except: 126 | logError("OpenWeatherMap failed to find forecast data for your location. Exiting process...", "", sourcename) 127 | exit() 128 | else: 129 | printC("Sucessfully parsed forecast response!", "green") 130 | 131 | # -- Modifies maintext and alttext -- # 132 | maintext = "Forecast" + "\n" + place_name 133 | alttext = "Tomorrow's Forecast: " + groupList[1].groupName 134 | 135 | return groupList, maintext, alttext 136 | ### YOUR CODE HERE ### 137 | 138 | def GenerateCard(): 139 | 140 | # Get data 141 | groupList, maintext, alttext = GetCardData() 142 | 143 | # If data is present 144 | if groupList: 145 | # Calculate card height 146 | tilesX = 4 147 | tilesY = math.floor(len(groupList)/2)+1 148 | printC("There are " + str(len(groupList)) + " groups in this Card. The card is " + str(tilesY) + " units high.", "yellow") 149 | 150 | # Stuff 151 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 152 | imageresx = tilesX*dpifactor 153 | imageresy = tilesY*dpifactor 154 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 155 | imagedraw = ImageDraw.Draw(image) 156 | backgroundcolor = COLORS[3] # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 157 | maintextcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 158 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 13*round(dpifactor/50)) 159 | 160 | 161 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 162 | padding = dpifactor/50 163 | top = padding 164 | for group in groupList: 165 | printC("Getting Image for group " + group.groupName) 166 | try: 167 | groupImg = group.Image((round(imageresx-(padding*2)),round(11*dpifactor/24)), dpifactor/10) 168 | except: 169 | import traceback 170 | logError("Unknown error with group " + group.groupName + "! Moving on to next group...", traceback.format_exc(), sourcename) 171 | continue 172 | image.paste(groupImg, (round(padding), round(top)), mask = groupImg) 173 | top += (11*dpifactor/24) + padding 174 | imagedraw.text((round(padding), round(top)), maintext, fill=maintextcolor, font=maintextfont) 175 | else: 176 | printC("No data! Sending null data.", "red") 177 | return None, None, None, None 178 | 179 | return image, alttext, tilesX, tilesY 180 | 181 | # Item class in ObjectGroup 182 | class Item: 183 | itemName = "" 184 | iconLocation = "" # Icon file path 185 | bgColor = (0,0,0) # Background color of item 186 | bgFillAmt = 1 # Percentage (0 - 1) of background filledness, useful to show smart light brightness 187 | 188 | def __init__(self, itemName, iconLocation, bgColor, bgFillAmt=1): 189 | self.itemName = itemName 190 | self.iconLocation = iconLocation 191 | self.bgColor = bgColor 192 | self.bgFillAmt = bgFillAmt 193 | print("New Item | " + self.itemName + " with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor)) 194 | 195 | # Returns a pretty Item with a rounded-rectangle background 196 | def Image(self, xyres, cornerrad): 197 | 198 | # Create the image of provided size 199 | image = Image.new(mode="RGBA", size = (xyres, xyres)) 200 | imagedraw = ImageDraw.Draw(image) 201 | 202 | # This background changes height based on the fill amount. Useful for smart light brightness or speaker volume. 203 | imagedraw.rounded_rectangle([(0,0),(xyres,xyres)], fill=(255,255,255,100), radius=cornerrad) # BG 204 | imagedraw.rounded_rectangle([(0,round(xyres*(1-self.bgFillAmt))),(xyres,xyres)], fill=(round(self.bgColor[0]), round(self.bgColor[1]), round(self.bgColor[2])), radius=round(cornerrad)) # FG 205 | 206 | # Overlay the icon 207 | icon = Image.open(self.iconLocation) 208 | icon = icon.resize((round(xyres-(xyres/12)), round(xyres-(xyres/12)))) 209 | image.paste(icon, (round(xyres/25), round(xyres/25)), mask=icon) 210 | 211 | return image 212 | 213 | def __str__(self): 214 | return "Item object: " + self.itemName + "' with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor) 215 | 216 | # Group class in ObjectGroup 217 | class Group: 218 | groupName = "" 219 | itemArray = [] 220 | 221 | def __init__(self, groupName, itemArray): 222 | self.groupName = groupName 223 | self.itemArray = itemArray 224 | 225 | nameList = "" 226 | for item in self.itemArray: 227 | nameList += item.itemName 228 | nameList += ", " 229 | print("New ItemGroup | " + self.groupName + " with items " + nameList) 230 | 231 | def Image(self, xyres, cornerrad): 232 | # Create image of provided size 233 | image = Image.new(mode="RGBA", size = xyres) 234 | imagedraw = ImageDraw.Draw(image) 235 | 236 | # Create background 237 | imagedraw.rounded_rectangle([(0,0),xyres], fill=(0,0,0, 100), radius=cornerrad) 238 | 239 | # Overlay Items 240 | padding = round(((2*xyres[0]/3)/6)/20) 241 | imageWidth = round(((2*xyres[0]/3)/6)-(padding*2)) 242 | leftmost = padding 243 | for item in self.itemArray: 244 | try: 245 | itemImage = item.Image(imageWidth, cornerrad) 246 | except: 247 | import traceback 248 | logError("Unknown error with image " + image.imageName + "! Moving on to next image...", traceback.format_exc(), sourcename) 249 | continue 250 | image.paste(itemImage, (leftmost, padding), mask=itemImage) 251 | leftmost += imageWidth+(padding) 252 | fontscalefactor = 15 / (self.groupName.count("\n")+1) 253 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", round(fontscalefactor*padding)) 254 | imagedraw.text((leftmost+padding, 0), self.groupName, font=maintextfont, fill=COLORS[1]) 255 | 256 | return image 257 | 258 | def __str__(self): 259 | nameList = "" 260 | for item in self.itemArray: 261 | nameList += item.itemName 262 | nameList += ", " 263 | print("ItemGroup: " + self.groupName + " with items " + nameList) 264 | 265 | def printC(string, color = "white"): 266 | from termcolor import colored 267 | print(sourcename + " | " + colored(str(string), color)) 268 | 269 | def GetPresets(): 270 | # Set presets 271 | printC("Getting deps...", "blue") 272 | currentLocation = os.getcwd().replace('\\', '/') 273 | nextindex = currentLocation.rfind("/") 274 | global SMARTFRAMEFOLDER 275 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 276 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 277 | else: 278 | SMARTFRAMEFOLDER = currentLocation 279 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 280 | 281 | sys.path.append(SMARTFRAMEFOLDER) 282 | 283 | printC("Gathering colors...", "blue") 284 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 285 | colorfileLines = colorfile.readlines() 286 | global COLORS 287 | for line in colorfileLines: 288 | if "#" in line: 289 | break 290 | else: 291 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 292 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 293 | 294 | GetPresets() 295 | from ErrorLogger import logError 296 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 297 | def GetCard(): 298 | 299 | # Generate card... 300 | printC("Starting card generation...", "blue") 301 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 302 | 303 | # Check if card exists 304 | if image and alttext and tilesX and tilesY: 305 | printC("Finished generating card!...", "green") 306 | 307 | 308 | # Setup output location 309 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 310 | printC("Will output to " + outputLocation, "cyan") 311 | 312 | # Save 313 | image.save(outputLocation) 314 | printC("Image saved to " + outputLocation + "!", "green") 315 | 316 | from Card import Card 317 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 318 | else: 319 | # No cards 320 | printC("No cards to return!...", "red") 321 | return None 322 | 323 | if __name__ == "__main__": 324 | GetCard() 325 | -------------------------------------------------------------------------------- /Available Plugins/LibraryGoBrr.py: -------------------------------------------------------------------------------- 1 | # RaddedMC's SmartFrame v2 -- LibraryGoBrr.py 2 | # Hi James 3 | # Library always go brr 4 | 5 | # Required deps: Pillow, termcolor, Reqests, beautifulSoup 6 | 7 | # Add any user-definable variables here! (API keys, usernames, etc.) 8 | sourcename = "WeldonLibraryOccupancy" 9 | 10 | from PIL import Image, ImageFont, ImageDraw 11 | import os 12 | import sys 13 | import math 14 | import requests 15 | from bs4 import BeautifulSoup as soup 16 | import traceback 17 | 18 | 19 | SMARTFRAMEFOLDER = "" 20 | COLORS = [] 21 | 22 | #### YOUR CODE HERE #### 23 | def GetCardData(): 24 | count = 21 25 | maintext = "" 26 | alttext = "" 27 | 28 | # -- Parsing D.B. Weldon website for data -- # 29 | page_url = "https://www.lib.uwo.ca/taps/tapper?lib=wel" 30 | try: 31 | printC("Attempting to get library occupancy data.", "yellow") 32 | response = requests.get(page_url) 33 | except: 34 | logError("Failed to gather library occupancy data. Exiting process...", "", sourcename) 35 | exit() 36 | else: 37 | printC("Sucessfully gathered library occupancy data!", "green") 38 | 39 | page_soup = soup(response.content, "html.parser") 40 | div = page_soup.find(id="current").text 41 | 42 | # -- String parsing from HTML content of website -- # 43 | num = "" # Makes empty string 44 | for c in div: 45 | if c.isdigit(): 46 | num = num + c # Assembles number string 47 | 48 | # -- Card variables -- # 49 | count = int(num) 50 | maintext = " Occupants\n in DB Weldon" 51 | alttext = num + " occupants in Weldon." 52 | 53 | printC(alttext,"blue") 54 | 55 | return count, maintext, alttext 56 | #### YOUR CODE HERE #### 57 | 58 | def GenerateCard(): 59 | # EDIT THESE TO CUSTOMIZE YOUR PLUGIN'S APPEARANCE! 60 | tilesX = 2 # I don't recommend changing these values for this template 61 | tilesY = 2 # I don't recommend changing these values for this template 62 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 63 | backgroundcolor = COLORS[3] # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 64 | textcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 65 | circlesbgcolor = (COLORS[3][0]-10, COLORS[3][1]-10, COLORS[3][2]-10) # Change this to a 3-value tuple to change the background color of your progress meter! 66 | printC("Counter circles background color is " + str(circlesbgcolor)) 67 | 68 | imageresx = tilesX*dpifactor 69 | imageresy = tilesY*dpifactor 70 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 71 | imagedraw = ImageDraw.Draw(image) 72 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 73 | 74 | count, maintext, alttext = GetCardData() 75 | 76 | if maintext and alttext: 77 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 15*round(dpifactor/75)) 78 | if count < 10: # Don't worry i hate this too 79 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 80 | elif count < 20: 81 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 82 | elif count < 100: 83 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 84 | elif count < 1000: 85 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 40*round(dpifactor/50)) 86 | elif count < 10000: 87 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 35*round(dpifactor/50)) 88 | elif count < 100000: 89 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 30*round(dpifactor/50)) 90 | else: 91 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 20*round(dpifactor/50)) 92 | 93 | # Logarithm 94 | try: 95 | logAmt = math.log((count),10) 96 | except ValueError: 97 | logAmt = 1 98 | if logAmt == 0: 99 | logAmt = 1 100 | printC("LogAmt is " + str(logAmt)) 101 | 102 | # Top position 103 | topdivamt = (128/(5*logAmt)) 104 | if topdivamt < 3: 105 | topdivamt = 3 106 | counttexttop = imageresx/topdivamt 107 | 108 | # Start position 109 | startdivamt = (2.2*logAmt)+3 110 | if count < 10: 111 | startdivamt = 3 112 | counttextstart = imageresx/startdivamt 113 | 114 | printC("Counter scale factors are (" + str(startdivamt) + ", " + str(topdivamt) + ")") 115 | 116 | # Circles 117 | if not count == 0: 118 | cols = math.ceil(math.sqrt(count)) 119 | rows = round(math.sqrt(count)) 120 | printC("Generating a ("+str(cols)+"x"+str(rows)+") grid of " + str(count) + " circles...") 121 | 122 | padding = imageresx/(4*cols) 123 | size = (imageresx/cols) - padding 124 | for i in range(0,count): 125 | col = i % cols 126 | row = math.floor(i/cols) 127 | xpos = (padding/2)+(size+padding)*col 128 | ypos = (padding/2)+(size+padding)*row 129 | imagedraw.ellipse((xpos, ypos, xpos+size, ypos+size), fill=circlesbgcolor) 130 | 131 | imagedraw.text((dpifactor/50,5*imageresy/8), maintext, font=maintextfont, fill=textcolor) 132 | imagedraw.text((counttextstart, counttexttop), str(count), font=counttextfont, fill=textcolor) # Counter text 133 | else: 134 | printC("No data! Sending null data.", "red") 135 | return None, None, None, None 136 | 137 | return image, alttext, tilesX, tilesY 138 | 139 | def printC(string, color = "white"): 140 | from termcolor import colored 141 | print(sourcename + " | " + colored(str(string), color)) 142 | 143 | def GetPresets(): 144 | # Set presets 145 | printC("Getting deps...", "blue") 146 | currentLocation = os.getcwd().replace('\\', '/') 147 | nextindex = currentLocation.rfind("/") 148 | global SMARTFRAMEFOLDER 149 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 150 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 151 | else: 152 | SMARTFRAMEFOLDER = currentLocation 153 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 154 | 155 | sys.path.append(SMARTFRAMEFOLDER) 156 | 157 | printC("Gathering colors...", "blue") 158 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 159 | colorfileLines = colorfile.readlines() 160 | global COLORS 161 | for line in colorfileLines: 162 | if "#" in line: 163 | break 164 | else: 165 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 166 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 167 | 168 | GetPresets() 169 | from ErrorLogger import logError 170 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 171 | def GetCard(): 172 | 173 | # Generate card... 174 | printC("Starting card generation...", "blue") 175 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 176 | 177 | # Check if card exists 178 | if image and alttext and tilesX and tilesY: 179 | printC("Finished generating card!...", "green") 180 | 181 | 182 | # Setup output location 183 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 184 | printC("Will output to " + outputLocation, "cyan") 185 | 186 | # Save 187 | image.save(outputLocation) 188 | printC("Image saved to " + outputLocation + "!", "green") 189 | 190 | from Card import Card 191 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 192 | else: 193 | # No cards 194 | printC("No cards to return!...", "red") 195 | return None 196 | 197 | if __name__ == "__main__": 198 | GetCard() 199 | 200 | -------------------------------------------------------------------------------- /Available Plugins/Nekos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- Nekos.py 3 | # This plugin grabs a photo from https://nekos.life -- be careful visiting this website as it does contain NSFW content. 4 | 5 | # Required deps: Pillow, termcolor, nekos.py, wget 6 | 7 | import shutil 8 | from PIL import Image, ImageFont, ImageDraw, ImageOps 9 | import os 10 | import sys 11 | import nekos 12 | import random 13 | import requests 14 | 15 | SMARTFRAMEFOLDER = "" 16 | COLORS = [] 17 | 18 | uniqueneko = random.randrange(0,100000) # used to have more than one neko plugin with a very low chance of conflict 19 | sourcename = "Photo " + str(uniqueneko) + " from Nekos.life " 20 | 21 | #### YOUR CODE HERE #### 22 | def GetCardData(): 23 | 24 | global sourcename 25 | 26 | def GetPathWithinNeighbouringFolder(fileWithin, folder): 27 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 28 | index = file.rfind("/") 29 | file = file[:index] 30 | fullImagePath = file + "/" + folder + "/" + fileWithin # File location of image 31 | return fullImagePath 32 | 33 | 34 | # Connect to server 35 | printC("Downloading photo from nekos.life!", "blue") 36 | try: 37 | url = nekos.img("neko") 38 | printC(url) 39 | output = GetPathWithinNeighbouringFolder("mew"+str(uniqueneko)+".png", "") 40 | rfile = requests.get(url, stream=True) 41 | if rfile.status_code == 200: 42 | with open(output, "wb") as f: 43 | rfile.raw.decode_content = True 44 | shutil.copyfileobj(rfile.raw, f) 45 | except: 46 | import traceback 47 | logError("Error connecting to nekos.life! Check the traceback.", traceback.format_exc(), sourcename) 48 | return None, None, None 49 | 50 | 51 | # Meta stuff 52 | background = (255, 163, 163) 53 | alttext = "neko mew" 54 | 55 | return output, background, alttext 56 | #### YOUR CODE HERE #### 57 | 58 | def GenerateCard(): 59 | imageFile, background, alttext = GetCardData() 60 | 61 | if not imageFile == "" and imageFile: 62 | try: 63 | printC("Opening image..." , "blue") 64 | icon = Image.open(imageFile) 65 | icon = ImageOps.exif_transpose(icon) 66 | except: 67 | import traceback 68 | logError("Unable to open image! Check the traceback.", traceback.format_exc(), sourcename) 69 | return None, None, None, None 70 | else: 71 | printC("No image! Sending null data.", "red") 72 | return None, None, None, None 73 | 74 | vertical = False 75 | if icon.size[1] >= icon.size[0]: 76 | # Tall image 77 | vertical = True 78 | printC("Tall photo! " + str(icon.size[0]) + " x " + str(icon.size[1])) 79 | choice = random.randint(0,1) 80 | if choice == 0: 81 | tilesX = 2 82 | tilesY = 2 83 | elif choice == 1: 84 | tilesX = 4 85 | tilesY = 4 86 | else: 87 | # Not tall image 88 | printC("Not tall photo! " + str(icon.size[0]) + " x " + str(icon.size[1])) 89 | choice = random.randint(0,2) 90 | if choice == 0: 91 | tilesX = 2 92 | tilesY = 2 93 | elif choice == 1: 94 | tilesX = 4 95 | tilesY = 4 96 | elif choice == 2: 97 | tilesX = 4 98 | tilesY = 2 99 | 100 | printC("Card size is " + str(tilesX) + " x " + str(tilesY), "blue") 101 | 102 | dpifactor = 300 # Change this to increase card resolution. Don't go too high!!! 103 | imageresx = tilesX*dpifactor 104 | imageresy = tilesY*dpifactor 105 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 106 | imagedraw = ImageDraw.Draw(image) 107 | 108 | if imageFile and background and alttext: 109 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=background) 110 | 111 | width = round(imageresx-(dpifactor/12)) 112 | height = round(imageresy-(dpifactor/12)) 113 | 114 | printC("Resizing image to " + str(width) + "x" + str(height)) 115 | 116 | if vertical or (tilesX == 4 and tilesY == 2): 117 | # Shrink x and y down to width of viewport 118 | wpercent = (width / float(icon.size[0])) 119 | hsize = int((float(icon.size[1]) * float(wpercent))) 120 | icon = icon.resize((width, hsize)) 121 | # crop top and bottom off to height of viewport (centered) 122 | 123 | else: 124 | # Shrink x and y down to height of viewport 125 | hpercent = (height / float(icon.size[1])) 126 | wsize = int((float(icon.size[0]) * float(hpercent))) 127 | icon = icon.resize((wsize, height)) 128 | 129 | vcentertop = (icon.size[1]/2)-(height/2) 130 | vcenterbottom = (icon.size[1]/2)+(height/2) 131 | hcenterleft = (icon.size[0]/2)-(width/2) 132 | hcenterright = (icon.size[0]/2)+(width/2) 133 | 134 | printC("Vertically centered to " + str(vcentertop) + ", " + str(vcenterbottom)) 135 | icon = icon.crop((hcenterleft, vcentertop, hcenterright, vcenterbottom)) 136 | 137 | try: 138 | image.paste(icon, (round(dpifactor/25), round(dpifactor/25)), mask=icon) 139 | except: 140 | printC("Error with transparency! Trying without...", "red") 141 | try: 142 | image.paste(icon, (round(dpifactor/25), round(dpifactor/25))) 143 | except: 144 | import traceback 145 | logError("Unable to display image! Check the traceback.", traceback.format_exc(), sourcename) 146 | return None, None, None, None 147 | 148 | try: 149 | printC("Deleting photo " + imageFile) 150 | os.remove(imageFile) 151 | except: 152 | import traceback 153 | logError("Unable to delete image! Check out the traceback!", traceback.format_exc(), sourcename) 154 | else: 155 | printC("No data! Sending null data.", "red") 156 | return None, None, None, None 157 | 158 | return image, alttext, tilesX, tilesY 159 | 160 | 161 | def printC(string, color = "white"): 162 | from termcolor import colored 163 | print(sourcename + " | " + colored(str(string), color)) 164 | 165 | def GetPresets(): 166 | # Set presets 167 | printC("Getting deps...", "blue") 168 | currentLocation = os.getcwd().replace('\\', '/') 169 | nextindex = currentLocation.rfind("/") 170 | global SMARTFRAMEFOLDER 171 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 172 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 173 | else: 174 | SMARTFRAMEFOLDER = currentLocation 175 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 176 | 177 | sys.path.append(SMARTFRAMEFOLDER) 178 | 179 | printC("Gathering colors...", "blue") 180 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 181 | colorfileLines = colorfile.readlines() 182 | global COLORS 183 | for line in colorfileLines: 184 | if "#" in line: 185 | break 186 | else: 187 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 188 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 189 | 190 | GetPresets() 191 | from ErrorLogger import logError 192 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 193 | def GetCard(): 194 | 195 | # Generate card... 196 | printC("Starting card generation...", "blue") 197 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 198 | 199 | # Check if card exists 200 | if image and alttext and tilesX and tilesY: 201 | printC("Finished generating card!...", "green") 202 | 203 | 204 | # Setup output location 205 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 206 | printC("Will output to " + outputLocation, "cyan") 207 | 208 | # Save 209 | image.save(outputLocation) 210 | printC("Image saved to " + outputLocation + "!", "green") 211 | 212 | from Card import Card 213 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 214 | else: 215 | # No cards 216 | printC("No cards to return!...", "red") 217 | return None 218 | 219 | if __name__ == "__main__": 220 | GetCard() 221 | -------------------------------------------------------------------------------- /Available Plugins/NewCovidCasesMLHU.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- NewCovidCasesMLHU.py by @Raminh05 3 | # This is a plugin to display daily new covid cases in the region of Middlesex-London, Ontario, Canada. 4 | # Middlesex-London COVID data from the Government of Ontario 5 | 6 | # Required deps for NewCasesMLHUCovid: Pillow, termcolor, requests, datetime, csv (should already be included) 7 | 8 | # No need to define any user variables! It just works. 9 | 10 | # Add any user-definable variables here! (API keys, usernames, etc.) 11 | sourcename = "NewCOVIDCasesMLHU" 12 | 13 | from PIL import Image, ImageFont, ImageDraw 14 | import os 15 | import sys 16 | import math 17 | 18 | SMARTFRAMEFOLDER = "" 19 | COLORS = [] 20 | 21 | #### YOUR CODE HERE #### 22 | def GetCardData(): 23 | count = 21 24 | maintext = "Some data" 25 | alttext = "Whatever you want!" 26 | 27 | # -- Import modules -- # 28 | from csv import reader 29 | import requests 30 | from datetime import datetime 31 | 32 | now = datetime.now() 33 | date = now.strftime("%Y-%m-%d") 34 | 35 | # -- Parses and returns case figures -- # 36 | def cases(): 37 | with open(SMARTFRAMEFOLDER + "/ontario_covid.csv", 'r') as f: 38 | data = list(reader(f)) 39 | cases = [i[16] for i in data[400::]] # Modifed from NewCovidCasesOntario to suit Middlesex region 40 | f.close() 41 | return cases 42 | 43 | def dates(): 44 | with open(SMARTFRAMEFOLDER + "/ontario_covid.csv", 'r') as f: 45 | data = list(reader(f)) 46 | dates = [i[0] for i in data[400::]] 47 | f.close() 48 | csv_date = dates[-1] 49 | return csv_date 50 | 51 | # -- Date and csv currency logi (re-written by @RaddedMC 08/13/21) -- # 52 | 53 | # Check if CSV Exists 54 | # If yes, run date check 55 | # If no, download new one 56 | 57 | # If date check successful, continue 58 | # If date check failed, download new file (unless already downloaded) 59 | 60 | def downloadFile(): 61 | try: 62 | r = requests.get('https://data.ontario.ca/dataset/f4f86e54-872d-43f8-8a86-3892fd3cb5e6/resource/8a88fe6d-d8fb-41a3-9d04-f0550a44999f/download/daily_change_in_cases_by_phu.csv') 63 | with open(SMARTFRAMEFOLDER + "/ontario_covid.csv", 'wb') as f: 64 | f.write(r.content) 65 | f.close() 66 | except: 67 | import traceback 68 | logError("Unable to download COVID information! Check the traceback.", traceback.format_exc(), sourcename) 69 | raise ConnectionError 70 | 71 | printC("Checking for a CSV file...", "blue") 72 | if os.path.isfile(SMARTFRAMEFOLDER + "/ontario_covid.csv"): 73 | printC("Found a file!", "green") 74 | csv_date = dates() 75 | print(csv_date) 76 | if csv_date != date: 77 | printC("File is out of date. Downloading a new one.", "red") 78 | try: 79 | downloadFile() 80 | except ConnectionError: 81 | return None, None 82 | else: 83 | printC("File is up to date!", "green") 84 | else: 85 | printC("File doesn't exist! Downloading a new one.", "red") 86 | try: 87 | downloadFile() 88 | except ConnectionError: 89 | return None, None 90 | 91 | # -- Fetches information from csv -- # 92 | case = cases() 93 | count = int(case[-1]) 94 | 95 | maintext = "New\nMiddlesex-London\nCOVID Cases Today" # Spaces to fix centering 96 | alttext = "There are " + str(count) + " new cases of COVID-19 in Middlesex-London today." 97 | 98 | return count, maintext, alttext 99 | #### YOUR CODE HERE #### 100 | 101 | def GenerateCard(): 102 | # EDIT THESE TO CUSTOMIZE YOUR PLUGIN'S APPEARANCE! 103 | tilesX = 2 # I don't recommend changing these values for this template 104 | tilesY = 2 # I don't recommend changing these values for this template 105 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 106 | backgroundcolor = COLORS[3] # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 107 | textcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 108 | circlesbgcolor = (COLORS[3][0]-10, COLORS[3][1]-10, COLORS[3][2]-10) # Change this to a 3-value tuple to change the background color of your progress meter! 109 | printC("Counter circles background color is " + str(circlesbgcolor)) 110 | 111 | imageresx = tilesX*dpifactor 112 | imageresy = tilesY*dpifactor 113 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 114 | imagedraw = ImageDraw.Draw(image) 115 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 116 | 117 | count, maintext, alttext = GetCardData() 118 | 119 | if maintext and alttext: 120 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 10*round(dpifactor/50)) 121 | if count in range(0, 1000): # Don't worry i hate this too 122 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 45*round(dpifactor/50)) # Change specifically made to fit this plugin 123 | elif count < 1000: 124 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 40*round(dpifactor/50)) 125 | elif count < 10000: 126 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 35*round(dpifactor/50)) 127 | elif count < 100000: 128 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 30*round(dpifactor/50)) 129 | else: 130 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 20*round(dpifactor/50)) 131 | 132 | # Logarithm 133 | try: 134 | logAmt = math.log((count),10) 135 | except ValueError: 136 | logAmt = 1 137 | if logAmt == 0: 138 | logAmt = 1 139 | printC("LogAmt is " + str(logAmt)) 140 | 141 | # Top position 142 | topdivamt = (128/(5*logAmt)) 143 | if topdivamt < 3: 144 | topdivamt = 3 145 | counttexttop = imageresx/topdivamt 146 | 147 | # Start position 148 | startdivamt = (2.2*logAmt)+3 149 | if count < 10: 150 | startdivamt = 3 151 | counttextstart = imageresx/startdivamt 152 | 153 | printC("Counter scale factors are (" + str(startdivamt) + ", " + str(topdivamt) + ")") 154 | 155 | # Circles 156 | if not count == 0: 157 | cols = math.ceil(math.sqrt(count)) 158 | rows = round(math.sqrt(count)) 159 | printC("Generating a ("+str(cols)+"x"+str(rows)+") grid of " + str(count) + " circles...") 160 | 161 | padding = imageresx/(4*cols) 162 | size = (imageresx/cols) - padding 163 | for i in range(0,count): 164 | col = i % cols 165 | row = math.floor(i/cols) 166 | xpos = (padding/2)+(size+padding)*col 167 | ypos = (padding/2)+(size+padding)*row 168 | imagedraw.ellipse((xpos, ypos, xpos+size, ypos+size), fill=circlesbgcolor) 169 | 170 | imagedraw.text((dpifactor/50,3*imageresy/5), maintext, font=maintextfont, fill=textcolor) 171 | imagedraw.text((counttextstart, counttexttop), str(count), font=counttextfont, fill=textcolor) # Counter text 172 | else: 173 | printC("No data! Sending null data.", "red") 174 | return None, None, None, None 175 | 176 | return image, alttext, tilesX, tilesY 177 | 178 | 179 | def printC(string, color = "white"): 180 | from termcolor import colored 181 | print(sourcename + " | " + colored(str(string), color)) 182 | 183 | def GetPresets(): 184 | # Set presets 185 | printC("Getting deps...", "blue") 186 | currentLocation = os.getcwd().replace('\\', '/') 187 | nextindex = currentLocation.rfind("/") 188 | global SMARTFRAMEFOLDER 189 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 190 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 191 | else: 192 | SMARTFRAMEFOLDER = currentLocation 193 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 194 | 195 | sys.path.append(SMARTFRAMEFOLDER) 196 | 197 | printC("Gathering colors...", "blue") 198 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 199 | colorfileLines = colorfile.readlines() 200 | global COLORS 201 | for line in colorfileLines: 202 | if "#" in line: 203 | break 204 | else: 205 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 206 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 207 | 208 | GetPresets() 209 | from ErrorLogger import logError 210 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 211 | def GetCard(): 212 | 213 | # Generate card... 214 | printC("Starting card generation...", "blue") 215 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 216 | 217 | # Check if card exists 218 | if image and alttext and tilesX and tilesY: 219 | printC("Finished generating card!...", "green") 220 | 221 | 222 | # Setup output location 223 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 224 | printC("Will output to " + outputLocation, "cyan") 225 | 226 | # Save 227 | image.save(outputLocation) 228 | printC("Image saved to " + outputLocation + "!", "green") 229 | 230 | from Card import Card 231 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 232 | else: 233 | # No cards 234 | printC("No cards to return!...", "red") 235 | return None 236 | 237 | if __name__ == "__main__": 238 | GetCard() 239 | -------------------------------------------------------------------------------- /Available Plugins/NewCovidCasesOntario.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- NewCovidCasesOntario.py by @Raminh05 3 | # This is a plugin to display daily new covid cases in the province of Ontario, Canada 4 | # Ontario COVID data from the Government of Ontario. 5 | # This particular template will let you show a single large number and some small text with a fancy background. 6 | 7 | # Required deps for NewCasesOntarioCovid: Pillow, termcolor, requests, datetime (csv is included in python!). 8 | 9 | # No need to define any user variables! It just works. 10 | 11 | # Add any user-definable variables here! (API keys, usernames, etc.) 12 | sourcename = "NewCovidCasesOntario" 13 | 14 | from PIL import Image, ImageFont, ImageDraw 15 | import os 16 | import sys 17 | import math 18 | 19 | SMARTFRAMEFOLDER = "" 20 | COLORS = [] 21 | 22 | #### YOUR CODE HERE #### 23 | def GetCardData(): 24 | count = 21 25 | maintext = "Some data" 26 | alttext = "Whatever you want!" 27 | 28 | # -- Import modules -- # 29 | from csv import reader 30 | import requests 31 | from datetime import datetime 32 | 33 | now = datetime.now() 34 | date = now.strftime("%Y-%m-%d") 35 | 36 | # -- Parses and returns case figures -- # 37 | def cases(): 38 | with open(SMARTFRAMEFOLDER + "/ontario_covid.csv", 'r') as f: 39 | data = list(reader(f)) 40 | cases = [i[35] for i in data[400::]] 41 | f.close() 42 | return cases 43 | 44 | def dates(): 45 | with open(SMARTFRAMEFOLDER + "/ontario_covid.csv", 'r') as f: 46 | data = list(reader(f)) 47 | dates = [i[0] for i in data[400::]] 48 | f.close() 49 | csv_date = dates[-1] 50 | return csv_date 51 | 52 | # -- Date and csv currency logic (re-written by @RaddedMC 08/13/21) -- # 53 | 54 | # Check if CSV Exists 55 | # If yes, run date check 56 | # If no, download new one 57 | 58 | # If date check successful, continue 59 | # If date check failed, download new file (unless already downloaded) 60 | 61 | def downloadFile(): 62 | try: 63 | r = requests.get('https://data.ontario.ca/dataset/f4f86e54-872d-43f8-8a86-3892fd3cb5e6/resource/8a88fe6d-d8fb-41a3-9d04-f0550a44999f/download/daily_change_in_cases_by_phu.csv') 64 | with open(SMARTFRAMEFOLDER + "/ontario_covid.csv", 'wb') as f: 65 | f.write(r.content) 66 | f.close() 67 | except: 68 | import traceback 69 | logError("Unable to download COVID information! Check the traceback.", traceback.format_exc(), sourcename) 70 | raise ConnectionError 71 | 72 | printC("Checking for a CSV file...", "blue") 73 | if os.path.isfile(SMARTFRAMEFOLDER + "/ontario_covid.csv"): 74 | printC("Found a file!", "green") 75 | csv_date = dates() 76 | print(csv_date) 77 | if csv_date != date: 78 | printC("File is out of date. Downloading a new one.", "red") 79 | try: 80 | downloadFile() 81 | except ConnectionError: 82 | return None, None 83 | else: 84 | printC("File is up to date!", "green") 85 | else: 86 | printC("File doesn't exist! Downloading a new one.", "red") 87 | try: 88 | downloadFile() 89 | except ConnectionError: 90 | return None, None 91 | 92 | # -- Fetches information from csv -- # 93 | case = cases() 94 | count = int(case[-1]) 95 | 96 | maintext = "New Ontario COVID\n Cases Today" # Spaces to fix centering 97 | alttext = "There are " + str(count) + " new cases of COVID-19 in Ontario today." 98 | 99 | 100 | return count, maintext, alttext 101 | #### YOUR CODE HERE #### 102 | 103 | def GenerateCard(): 104 | # EDIT THESE TO CUSTOMIZE YOUR PLUGIN'S APPEARANCE! 105 | tilesX = 2 # I don't recommend changing these values for this template 106 | tilesY = 2 # I don't recommend changing these values for this template 107 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 108 | backgroundcolor = COLORS[3] # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 109 | textcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 110 | circlesbgcolor = (COLORS[3][0]-10, COLORS[3][1]-10, COLORS[3][2]-10) # Change this to a 3-value tuple to change the background color of your progress meter! 111 | printC("Counter circles background color is " + str(circlesbgcolor)) 112 | 113 | imageresx = tilesX*dpifactor 114 | imageresy = tilesY*dpifactor 115 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 116 | imagedraw = ImageDraw.Draw(image) 117 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 118 | 119 | count, maintext, alttext = GetCardData() 120 | 121 | if maintext and alttext: 122 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 10*round(dpifactor/50)) 123 | if count < 10: # Don't worry i hate this too 124 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 125 | elif count < 20: 126 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 127 | elif count < 100: 128 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 129 | elif count < 1000: 130 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 40*round(dpifactor/50)) 131 | elif count < 10000: 132 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 35*round(dpifactor/50)) 133 | elif count < 100000: 134 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 30*round(dpifactor/50)) 135 | else: 136 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 20*round(dpifactor/50)) 137 | 138 | # Logarithm 139 | try: 140 | logAmt = math.log((count),10) 141 | except ValueError: 142 | logAmt = 1 143 | if logAmt == 0: 144 | logAmt = 1 145 | printC("LogAmt is " + str(logAmt)) 146 | 147 | # Top position 148 | topdivamt = (128/(5*logAmt)) 149 | if topdivamt < 3: 150 | topdivamt = 3 151 | counttexttop = imageresx/topdivamt 152 | 153 | # Start position 154 | startdivamt = (2.2*logAmt)+3 155 | if count < 10: 156 | startdivamt = 3 157 | counttextstart = imageresx/startdivamt 158 | 159 | printC("Counter scale factors are (" + str(startdivamt) + ", " + str(topdivamt) + ")") 160 | 161 | # Circles 162 | if not count == 0: 163 | cols = math.ceil(math.sqrt(count)) 164 | rows = round(math.sqrt(count)) 165 | printC("Generating a ("+str(cols)+"x"+str(rows)+") grid of " + str(count) + " circles...") 166 | 167 | padding = imageresx/(4*cols) 168 | size = (imageresx/cols) - padding 169 | for i in range(0,count): 170 | col = i % cols 171 | row = math.floor(i/cols) 172 | xpos = (padding/2)+(size+padding)*col 173 | ypos = (padding/2)+(size+padding)*row 174 | imagedraw.ellipse((xpos, ypos, xpos+size, ypos+size), fill=circlesbgcolor) 175 | 176 | imagedraw.text((dpifactor/50,3*imageresy/5), maintext, font=maintextfont, fill=textcolor) 177 | imagedraw.text((counttextstart, counttexttop), str(count), font=counttextfont, fill=textcolor) # Counter text 178 | else: 179 | printC("No data! Sending null data.", "red") 180 | return None, None, None, None 181 | 182 | return image, alttext, tilesX, tilesY 183 | 184 | 185 | def printC(string, color = "white"): 186 | from termcolor import colored 187 | print(sourcename + " | " + colored(str(string), color)) 188 | 189 | def GetPresets(): 190 | # Set presets 191 | printC("Getting deps...", "blue") 192 | currentLocation = os.getcwd().replace('\\', '/') 193 | nextindex = currentLocation.rfind("/") 194 | global SMARTFRAMEFOLDER 195 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 196 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 197 | else: 198 | SMARTFRAMEFOLDER = currentLocation 199 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 200 | 201 | sys.path.append(SMARTFRAMEFOLDER) 202 | 203 | printC("Gathering colors...", "blue") 204 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 205 | colorfileLines = colorfile.readlines() 206 | global COLORS 207 | for line in colorfileLines: 208 | if "#" in line: 209 | break 210 | else: 211 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 212 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 213 | 214 | GetPresets() 215 | from ErrorLogger import logError 216 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 217 | def GetCard(): 218 | 219 | # Generate card... 220 | printC("Starting card generation...", "blue") 221 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 222 | 223 | # Check if card exists 224 | if image and alttext and tilesX and tilesY: 225 | printC("Finished generating card!...", "green") 226 | 227 | 228 | # Setup output location 229 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 230 | printC("Will output to " + outputLocation, "cyan") 231 | 232 | # Save 233 | image.save(outputLocation) 234 | printC("Image saved to " + outputLocation + "!", "green") 235 | 236 | from Card import Card 237 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 238 | else: 239 | # No cards 240 | printC("No cards to return!...", "red") 241 | return None 242 | 243 | if __name__ == "__main__": 244 | GetCard() 245 | 246 | -------------------------------------------------------------------------------- /Available Plugins/NmapDevices.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- NmapDevices.py 3 | # This plugin uses nmap to get a list of devices on your network. Useful for sysadmins 4 | 5 | # Required deps: Pillow, termcolor, python-nmap 6 | # You may need to install nmap on your machine to use this plugin. See https://nmap.org/ or make an issue on GitHub if you're having problems! 7 | 8 | # This plugin will grab your device's local network IP (not the IP address that the angry script kiddie can use to DDoS your internet) 9 | # and scans your subnet (network) to discover other devices on your network -- then places their names into a nice list :) 10 | sourcename = "Nmap Device List" 11 | 12 | from PIL import Image, ImageFont, ImageDraw 13 | import os 14 | import sys 15 | import math 16 | import nmap 17 | import socket 18 | 19 | SMARTFRAMEFOLDER = "" 20 | COLORS = [] 21 | 22 | ### YOUR CODE HERE ### 23 | def GetCardData(): 24 | def GetPathWithinNeighbouringFolder(fileWithin, folder): 25 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 26 | index = file.rfind("/") 27 | file = file[:index] 28 | fullImagePath = file + "/" + folder + "/" + fileWithin # File location of image 29 | return fullImagePath 30 | 31 | groupList = [] 32 | maintext = "" 33 | alttext = "" 34 | 35 | try: 36 | printC("Getting nmap...") 37 | nm = nmap.PortScanner() 38 | 39 | printC("Getting scan range...", "blue") 40 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 41 | s.connect(('10.255.255.255', 1)) 42 | scanRange = s.getsockname()[0] + "/24" 43 | 44 | printC("Scanning range " + scanRange + "...", "blue") 45 | hosts = nm.scan(hosts=scanRange, arguments="-sn --open") 46 | 47 | numDevices = len(hosts['scan']) 48 | printC(str(numDevices) + " devices found!", "green") 49 | maintext = str(numDevices) + " total devices" 50 | alttext = "nmap found " + str(numDevices) + " devices on your network." 51 | 52 | printC("Shuffling device list...") 53 | import random 54 | hostlist = random.shuffle(list(hosts['scan'])) 55 | keys = list(hosts['scan'].keys()) 56 | random.shuffle(keys) 57 | 58 | deviceCount = (2*random.randrange(1,4))+1 59 | printC("Grabbing info for " + str(deviceCount) + " devices...", "cyan") 60 | 61 | for host in keys: 62 | if len(groupList) < deviceCount: 63 | hostname = hosts['scan'][host]['hostnames'][0]['name'] 64 | ip = host 65 | try: 66 | vendor = hosts['scan'][host]['vendor'][hosts['scan'][host]['addresses']['mac']] 67 | except: 68 | vendor = None 69 | printC("Unable to find vendor information for device " + host, "red") 70 | 71 | if hostname == None or hostname == "": 72 | line1 = host 73 | else: 74 | line1 = hostname + " -- " + host 75 | 76 | if (not vendor == None) and (not vendor == ""): 77 | line2 = "by " + vendor 78 | else: 79 | line2 = "" 80 | groupList.append(Group(line1 + "\n" + line2, "")) 81 | else: 82 | break 83 | except: 84 | import traceback 85 | logError("Unknown error finding devices! Check the traceback for more info...", traceback.format_exc(), sourcename) 86 | return None, None, None 87 | 88 | # Create an Item(name, iconFilePath, backgroundColorTuple, backgroundFillPercentage) for each item 89 | # Create a Group(name, arrayOfItems) for each group 90 | # Return a list of Groups to be generated 91 | # Use GetPathWithinNeighbouringFolder to get icons related to your plugin. 92 | 93 | return groupList, maintext, alttext 94 | ### YOUR CODE HERE ### 95 | 96 | 97 | 98 | def GenerateCard(): 99 | 100 | # Get data 101 | groupList, maintext, alttext = GetCardData() 102 | 103 | # If data is present 104 | if groupList: 105 | # Calculate card height 106 | tilesX = 4 107 | tilesY = math.floor(len(groupList)/2)+1 108 | printC("There are " + str(len(groupList)) + " groups in this Card. The card is " + str(tilesY) + " units high.", "yellow") 109 | 110 | # Stuff 111 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 112 | imageresx = tilesX*dpifactor 113 | imageresy = tilesY*dpifactor 114 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 115 | imagedraw = ImageDraw.Draw(image) 116 | backgroundcolor = COLORS[3] # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 117 | maintextcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 118 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 20*round(dpifactor/50)) 119 | 120 | 121 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 122 | padding = dpifactor/50 123 | top = padding 124 | for group in groupList: 125 | printC("Getting Image for group " + group.groupName) 126 | try: 127 | groupImg = group.Image((round(imageresx-(padding*2)),round(11*dpifactor/24)), dpifactor/10) 128 | except: 129 | import traceback 130 | logError("Unknown error with group " + group.groupName + "! Moving on to next group...", traceback.format_exc(), sourcename) 131 | continue 132 | image.paste(groupImg, (round(padding), round(top)), mask = groupImg) 133 | top += (11*dpifactor/24) + padding 134 | imagedraw.text((round(padding), round(top)), maintext, fill=maintextcolor, font=maintextfont) 135 | else: 136 | printC("No data! Sending null data.", "red") 137 | return None, None, None, None 138 | 139 | return image, alttext, tilesX, tilesY 140 | 141 | # Item class in ObjectGroup 142 | class Item: 143 | itemName = "" 144 | iconLocation = "" # Icon file path 145 | bgColor = (0,0,0) # Background color of item 146 | bgFillAmt = 1 # Percentage (0 - 1) of background filledness, useful to show smart light brightness 147 | 148 | def __init__(self, itemName, iconLocation, bgColor, bgFillAmt=1): 149 | self.itemName = itemName 150 | self.iconLocation = iconLocation 151 | self.bgColor = bgColor 152 | self.bgFillAmt = bgFillAmt 153 | print("New Item | " + self.itemName + " with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor)) 154 | 155 | # Returns a pretty Item with a rounded-rectangle background 156 | def Image(self, xyres, cornerrad): 157 | 158 | # Create the image of provided size 159 | image = Image.new(mode="RGBA", size = (xyres, xyres)) 160 | imagedraw = ImageDraw.Draw(image) 161 | 162 | # This background changes height based on the fill amount. Useful for smart light brightness or speaker volume. 163 | imagedraw.rounded_rectangle([(0,0),(xyres,xyres)], fill=(255,255,255,100), radius=cornerrad) # BG 164 | imagedraw.rounded_rectangle([(0,round(xyres*(1-self.bgFillAmt))),(xyres,xyres)], fill=(round(self.bgColor[0]), round(self.bgColor[1]), round(self.bgColor[2])), radius=round(cornerrad)) # FG 165 | 166 | # Overlay the icon 167 | icon = Image.open(self.iconLocation) 168 | icon = icon.resize((round(xyres-(xyres/12)), round(xyres-(xyres/12)))) 169 | image.paste(icon, (round(xyres/25), round(xyres/25)), mask=icon) 170 | 171 | return image 172 | 173 | def __str__(self): 174 | return "Item object: " + self.itemName + "' with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor) 175 | 176 | # Group class in ObjectGroup 177 | class Group: 178 | groupName = "" 179 | itemArray = [] 180 | 181 | def __init__(self, groupName, itemArray): 182 | self.groupName = groupName 183 | self.itemArray = itemArray 184 | 185 | nameList = "" 186 | for item in self.itemArray: 187 | nameList += item.itemName 188 | nameList += ", " 189 | print("New ItemGroup | " + self.groupName + " with items " + nameList) 190 | 191 | def Image(self, xyres, cornerrad): 192 | # Create image of provided size 193 | image = Image.new(mode="RGBA", size = xyres) 194 | imagedraw = ImageDraw.Draw(image) 195 | 196 | # Create background 197 | imagedraw.rounded_rectangle([(0,0),xyres], fill=(0,0,0, 100), radius=cornerrad) 198 | 199 | # Overlay Items 200 | padding = round(((2*xyres[0]/3)/6)/20) 201 | imageWidth = round(((2*xyres[0]/3)/6)-(padding*2)) 202 | leftmost = padding 203 | for item in self.itemArray: 204 | try: 205 | itemImage = item.Image(imageWidth, cornerrad) 206 | except: 207 | import traceback 208 | logError("Unknown error with image " + image.imageName + "! Moving on to next image...", traceback.format_exc(), sourcename) 209 | continue 210 | image.paste(itemImage, (leftmost, padding), mask=itemImage) 211 | leftmost += imageWidth+(padding) 212 | fontscalefactor = 15 / (self.groupName.count("\n")+1) 213 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", round(fontscalefactor*padding)) 214 | imagedraw.text((leftmost+padding, 0), self.groupName, font=maintextfont, fill=COLORS[1]) 215 | 216 | return image 217 | 218 | def __str__(self): 219 | nameList = "" 220 | for item in self.itemArray: 221 | nameList += item.itemName 222 | nameList += ", " 223 | print("ItemGroup: " + self.groupName + " with items " + nameList) 224 | 225 | def printC(string, color = "white"): 226 | from termcolor import colored 227 | print(sourcename + " | " + colored(str(string), color)) 228 | 229 | def GetPresets(): 230 | # Set presets 231 | printC("Getting deps...", "blue") 232 | currentLocation = os.getcwd().replace('\\', '/') 233 | nextindex = currentLocation.rfind("/") 234 | global SMARTFRAMEFOLDER 235 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 236 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 237 | else: 238 | SMARTFRAMEFOLDER = currentLocation 239 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 240 | 241 | sys.path.append(SMARTFRAMEFOLDER) 242 | 243 | printC("Gathering colors...", "blue") 244 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 245 | colorfileLines = colorfile.readlines() 246 | global COLORS 247 | for line in colorfileLines: 248 | if "#" in line: 249 | break 250 | else: 251 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 252 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 253 | 254 | GetPresets() 255 | from ErrorLogger import logError 256 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 257 | def GetCard(): 258 | 259 | # Generate card... 260 | printC("Starting card generation...", "blue") 261 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 262 | 263 | # Check if card exists 264 | if image and alttext and tilesX and tilesY: 265 | printC("Finished generating card!...", "green") 266 | 267 | 268 | # Setup output location 269 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 270 | printC("Will output to " + outputLocation, "cyan") 271 | 272 | # Save 273 | image.save(outputLocation) 274 | printC("Image saved to " + outputLocation + "!", "green") 275 | 276 | from Card import Card 277 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 278 | else: 279 | # No cards 280 | printC("No cards to return!...", "red") 281 | return None 282 | 283 | if __name__ == "__main__": 284 | GetCard() 285 | -------------------------------------------------------------------------------- /Available Plugins/Philips Hue.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's Philips Hue plugin for SmartFrame -- Philips Hue.py 3 | # This plugin gets the state of your Philips Hue smart lights. 4 | 5 | # SETUP: Pair your SmartFrame device to your Bridge by pressing the 'link' button on the bridge BEFORE running SmartFrame. 6 | # Paste the IP address of your Philips Hue bridge into the hupipaddr variable. 7 | 8 | # Required deps: Pillow, termcolor, phue 9 | 10 | sourcename = "Philips Hue" 11 | hueipaddr = "192.168.1.150" 12 | 13 | from PIL import Image, ImageFont, ImageDraw 14 | import os 15 | import sys 16 | import math 17 | 18 | SMARTFRAMEFOLDER = "" 19 | COLORS = [] 20 | 21 | ### YOUR CODE HERE ### 22 | def GetCardData(): 23 | def GetPathWithinNeighbouringFolder(fileWithin, folder): 24 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 25 | index = file.rfind("/") 26 | file = file[:index] 27 | fullImagePath = file + "/" + folder + "/" + fileWithin # File location of image 28 | return fullImagePath 29 | 30 | groupList = [] 31 | 32 | # Each Item is a light. 33 | # Icon is GetPathWithinNeighbouringFolder("light.png", "Philips Hue") 34 | # bgColor is color of light * brightness 35 | # bgFillAmt is brightness 36 | 37 | # Import Bridge and connect 38 | from phue import Bridge 39 | try: 40 | global hueipaddr 41 | b = Bridge(hueipaddr) 42 | b.connect() 43 | except: 44 | printC("Error with Philips Hue! Have you paired your bridge?", "red") 45 | import traceback 46 | traceback.print_exc() 47 | return None, None, None 48 | 49 | # For every group... 50 | for group in b.groups: 51 | if group.name.startswith("hgrp"): # Some Hue plugin services will add random groups of lights that start with hgrp-###, I don't want these displayed lel 52 | continue 53 | lightlist = [] 54 | # For every light that's on 55 | for light in group.lights: 56 | if not light.reachable: 57 | printC("The " + light.name + " is not reachable.", "yellow") 58 | lightlist.append(Item(light.name, GetPathWithinNeighbouringFolder("light.png", "Philips Hue"), (125,0,0), 1)) 59 | elif light.on: 60 | try: 61 | hue = light.hue 62 | saturation = light.saturation 63 | import colorsys 64 | color = colorsys.rgb_to_hsv(hue/360, saturation/255, light.brightness/255) # This code is untested. Please let me know if your RGB Hue lights look different. 65 | except: 66 | printC("This light is not RGB. Defaulting to white color") 67 | color = (255*(light.brightness/255), 245*(light.brightness/255), 220*(light.brightness/255)) 68 | lightlist.append(Item(light.name, GetPathWithinNeighbouringFolder("light.png", "Philips Hue"), color, light.brightness/255)) 69 | 70 | if not lightlist: 71 | printC("There are no lights on in group " + group.name + ". Skipping...") 72 | else: 73 | printedname = group.name 74 | if not group.name.lower().endswith("lights") and not group.name.lower().endswith("light") and not group.name.lower().endswith("lamp") and not group.name.lower().endswith("strip"): 75 | printedname += " Lights" 76 | groupList.append(Group(printedname, lightlist)) 77 | 78 | # Do not add an Item if the light is off 79 | 80 | # Each Group is a room. 81 | # The group's name is the room 82 | 83 | numberOfLights = 0 84 | for group in groupList: 85 | for item in group.itemArray: 86 | numberOfLights += 1 87 | 88 | maintext = "Philips Hue" 89 | alttext = "Philips Hue: There are " + str(numberOfLights) + " lights on." 90 | 91 | # YOUR CODE HERE! Good luck, soldier. 92 | 93 | return groupList, maintext, alttext 94 | ### YOUR CODE HERE ### 95 | 96 | 97 | 98 | def GenerateCard(): 99 | 100 | # Get data 101 | groupList, maintext, alttext = GetCardData() 102 | 103 | # If data is present 104 | if groupList: 105 | # Calculate card height 106 | tilesX = 4 107 | tilesY = math.floor(len(groupList)/2)+1 108 | printC("There are " + str(len(groupList)) + " groups in this Card. The card is " + str(tilesY) + " units high.", "yellow") 109 | 110 | # Stuff 111 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 112 | imageresx = tilesX*dpifactor 113 | imageresy = tilesY*dpifactor 114 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 115 | imagedraw = ImageDraw.Draw(image) 116 | backgroundcolor = (181, 163, 73) # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 117 | maintextcolor = COLORS[0] # Change this to a 3-value tuple to change the text colour! 118 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 20*round(dpifactor/50)) 119 | 120 | 121 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 122 | padding = dpifactor/50 123 | top = padding 124 | for group in groupList: 125 | printC("Getting Image for group " + group.groupName) 126 | try: 127 | groupImg = group.Image((round(imageresx-(padding*2)),round(11*dpifactor/24)), dpifactor/10) 128 | except: 129 | printC("Unknown error with group " + group.groupName + "! Moving on to next group...", "red") 130 | import traceback 131 | traceback.print_exc() 132 | continue 133 | image.paste(groupImg, (round(padding), round(top)), mask = groupImg) 134 | top += (11*dpifactor/24) + padding 135 | imagedraw.text((padding, top), maintext, fill=maintextcolor, font=maintextfont) 136 | else: 137 | printC("No data! Sending null data.", "red") 138 | return None, None, None, None 139 | 140 | return image, alttext, tilesX, tilesY 141 | 142 | # Item class in ObjectGroup 143 | class Item: 144 | itemName = "" 145 | iconLocation = "" # Icon file path 146 | bgColor = (0,0,0) # Background color of item 147 | bgFillAmt = 1 # Percentage (0 - 1) of background filledness, useful to show smart light brightness 148 | 149 | def __init__(self, itemName, iconLocation, bgColor, bgFillAmt=1): 150 | self.itemName = itemName 151 | self.iconLocation = iconLocation 152 | self.bgColor = bgColor 153 | self.bgFillAmt = bgFillAmt 154 | print("New Item | " + self.itemName + " with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor)) 155 | 156 | # Returns a pretty Item with a rounded-rectangle background 157 | def Image(self, xyres, cornerrad): 158 | 159 | # Create the image of provided size 160 | image = Image.new(mode="RGBA", size = (xyres, xyres)) 161 | imagedraw = ImageDraw.Draw(image) 162 | 163 | # This background changes height based on the fill amount. Useful for smart light brightness or speaker volume. 164 | imagedraw.rounded_rectangle([(0,0),(xyres,xyres)], fill=(255,255,255,100), radius=cornerrad) # BG 165 | imagedraw.rounded_rectangle([(0,round(xyres*(1-self.bgFillAmt))),(xyres,xyres)], fill=(round(self.bgColor[0]), round(self.bgColor[1]), round(self.bgColor[2])), radius=round(cornerrad)) # FG 166 | 167 | # Overlay the icon 168 | icon = Image.open(self.iconLocation) 169 | icon = icon.resize((round(xyres-(xyres/12)), round(xyres-(xyres/12)))) 170 | image.paste(icon, (round(xyres/25), round(xyres/25)), mask=icon) 171 | 172 | return image 173 | 174 | def __str__(self): 175 | return "Item object: " + self.itemName + "' with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor) 176 | 177 | # Group class in ObjectGroup 178 | class Group: 179 | groupName = "" 180 | itemArray = [] 181 | 182 | def __init__(self, groupName, itemArray): 183 | self.groupName = groupName 184 | self.itemArray = itemArray 185 | 186 | nameList = "" 187 | for item in self.itemArray: 188 | nameList += item.itemName 189 | nameList += ", " 190 | print("New ItemGroup | " + self.groupName + " with items " + nameList) 191 | 192 | def Image(self, xyres, cornerrad): 193 | # Create image of provided size 194 | image = Image.new(mode="RGBA", size = xyres) 195 | imagedraw = ImageDraw.Draw(image) 196 | 197 | # Create background 198 | imagedraw.rounded_rectangle([(0,0),xyres], fill=(0,0,0, 100), radius=cornerrad) 199 | 200 | # Overlay Items 201 | padding = round(((2*xyres[0]/3)/6)/20) 202 | imageWidth = round(((2*xyres[0]/3)/6)-(padding*2)) 203 | leftmost = padding 204 | for item in self.itemArray: 205 | try: 206 | itemImage = item.Image(imageWidth, cornerrad) 207 | except: 208 | import traceback 209 | logError("Unknown error with image " + group.imageName + "! Moving on to next image...", traceback.format_exc(), sourcename) 210 | continue 211 | image.paste(itemImage, (leftmost, padding), mask=itemImage) 212 | leftmost += imageWidth+(padding) 213 | fontscalefactor = 15 / (self.groupName.count("\n")+1) 214 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", round(fontscalefactor*padding)) 215 | imagedraw.text((leftmost+padding, 0), self.groupName, font=maintextfont, fill=COLORS[1]) 216 | 217 | return image 218 | 219 | def __str__(self): 220 | nameList = "" 221 | for item in self.itemArray: 222 | nameList += item.itemName 223 | nameList += ", " 224 | print("ItemGroup: " + self.groupName + " with items " + nameList) 225 | 226 | def printC(string, color = "white"): 227 | from termcolor import colored 228 | print(sourcename + " | " + colored(str(string), color)) 229 | 230 | def GetPresets(): 231 | # Set presets 232 | printC("Getting deps...", "blue") 233 | currentLocation = os.getcwd().replace('\\', '/') 234 | nextindex = currentLocation.rfind("/") 235 | global SMARTFRAMEFOLDER 236 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 237 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 238 | else: 239 | SMARTFRAMEFOLDER = currentLocation 240 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 241 | 242 | sys.path.append(SMARTFRAMEFOLDER) 243 | 244 | printC("Gathering colors...", "blue") 245 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 246 | colorfileLines = colorfile.readlines() 247 | global COLORS 248 | for line in colorfileLines: 249 | if "#" in line: 250 | break 251 | else: 252 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 253 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 254 | 255 | GetPresets() 256 | from ErrorLogger import logError 257 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 258 | def GetCard(): 259 | 260 | # Generate card... 261 | printC("Starting card generation...", "blue") 262 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 263 | 264 | # Check if card exists 265 | if image and alttext and tilesX and tilesY: 266 | printC("Finished generating card!...", "green") 267 | 268 | 269 | # Setup output location 270 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 271 | printC("Will output to " + outputLocation, "cyan") 272 | 273 | # Save 274 | image.save(outputLocation) 275 | printC("Image saved to " + outputLocation + "!", "green") 276 | 277 | from Card import Card 278 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 279 | else: 280 | # No cards 281 | printC("No cards to return!...", "red") 282 | return None 283 | 284 | if __name__ == "__main__": 285 | GetCard() -------------------------------------------------------------------------------- /Available Plugins/Philips Hue/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/Philips Hue/light.png -------------------------------------------------------------------------------- /Available Plugins/SCPPhoto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- SCPPhoto.py 3 | # This plugin grabs a random photo from a Linux-based server with SCP enabled on your network and displays it. 4 | # SmartFrame is now one step closer to being a DumbFrame! 5 | 6 | # Setup: Enter your server's IP, username, password, and the path you want to scan for photos in the below variables. 7 | # This plugin does not scan recursively, only the root of the folder you specify in sshPath. 8 | # Currently this script only supports Linux servers, but you do *not* need SMB. SSH and SFTP are sufficient. 9 | 10 | # Required deps: Pillow, termcolor, paramiko 11 | 12 | 13 | sshIP = "192.168.1.133" 14 | sshUsername = "" 15 | sshPWD = "" 16 | sshPath = "" # Don't worry about escaping spaces! 17 | 18 | sourcename = "Photo from SCP " 19 | 20 | from PIL import Image, ImageFont, ImageDraw, ImageOps 21 | import os 22 | import sys 23 | import paramiko 24 | import random 25 | 26 | SMARTFRAMEFOLDER = "" 27 | COLORS = [] 28 | 29 | #### YOUR CODE HERE #### 30 | def GetCardData(): 31 | 32 | global sourcename 33 | 34 | def GetPathWithinNeighbouringFolder(fileWithin, folder): 35 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 36 | index = file.rfind("/") 37 | file = file[:index] 38 | fullImagePath = file + "/" + folder + "/" + fileWithin # File location of image 39 | return fullImagePath 40 | 41 | 42 | # Connect to server 43 | printC("Connecting to "+sshIP+"! ", "blue") 44 | 45 | ssh = paramiko.SSHClient() 46 | ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 47 | try: 48 | ssh.connect(sshIP, username=sshUsername, password=sshPWD, timeout=1) 49 | except: 50 | import traceback 51 | logError("Error connecting to server! Check the traceback.", traceback.format_exc(), sourcename) 52 | return None, None, None 53 | 54 | 55 | # Get number of images 56 | global sshPath 57 | fileCountCommand = "shopt -s nocaseglob; cd '" + sshPath + "'; ls *.jpg *.png *.bmp *.jpeg | wc -l" 58 | 59 | printC("Executing " + fileCountCommand + " on server...") 60 | try: 61 | fileCountCommandSTDIN, fileCountCommandSTDOUT, fileCountCommandSTDERR = ssh.exec_command(fileCountCommand) 62 | except: 63 | import traceback 64 | logError("Error executing command! Check the traceback.", traceback.format_exc(), sourcename) 65 | return None, None, None 66 | 67 | fileCount = int(fileCountCommandSTDOUT.read()) 68 | if fileCount <= 0: 69 | import traceback 70 | logError("No images on the server! Check the STDERR output.", fileCountCommandSTDERR.read(), sourcename) 71 | return None, None, None 72 | printC("There are " + str(fileCount) + " images on this server.", "yellow") 73 | 74 | 75 | # Choose image randomly 76 | fileNumberChosen = random.randint(1, fileCount) 77 | printC("Chosen photo #" + str(fileNumberChosen) + ".") 78 | 79 | # File filename from chosen image 80 | fileNameCommand = "shopt -s nocaseglob; cd '" + sshPath + "'; ls *.jpg *.png *.bmp *.jpeg | sed -n " + str(fileNumberChosen) + "p" 81 | printC("Executing " + fileNameCommand + " on server...") 82 | try: 83 | fileNameCommandSTDIN, fileNameCommandSTDOUT, fileNameCommandSTDERR = ssh.exec_command(fileNameCommand) 84 | except: 85 | import traceback 86 | logError("Error executing command! Check the traceback.", traceback.format_exc(), sourcename) 87 | return None, None, None 88 | 89 | try: 90 | fileName = fileNameCommandSTDOUT.readlines()[0].rstrip() 91 | except: 92 | import traceback 93 | logError("Can't find the file! Check the traceback and stderr.", traceback.format_exc() + "\n" + fileNameCommandSTDERR.read(), sourcename) 94 | return None, None, None 95 | 96 | if fileName == "": 97 | import traceback 98 | logError("Can't find the file! Check stderr.", fileNameCommandSTDERR.read(), sourcename) 99 | return None, None, None 100 | 101 | sourcename += fileName 102 | 103 | # Parse destination path 104 | imageDestPath = GetPathWithinNeighbouringFolder(fileName, "") 105 | imageSourcePath = sshPath.replace('\\', '/') 106 | if not imageSourcePath[-1] == "/": 107 | imageSourcePath += "/" 108 | imageSourcePath += fileName 109 | 110 | # Download photo 111 | printC("Opening SFTP...", "blue") 112 | try: 113 | ftp = ssh.open_sftp() 114 | except: 115 | import traceback 116 | logError("Error opening SFTP! Check the traceback.", traceback.format_exc(), sourcename) 117 | return None, None, None 118 | 119 | printC("Downloading " + imageSourcePath + " to " + imageDestPath, "blue") 120 | 121 | try: 122 | ftp.get(imageSourcePath, imageDestPath) 123 | except: 124 | import traceback 125 | logError("Error downloading photo! Check the traceback.", traceback.format_exc(), sourcename) 126 | return None, None, None 127 | 128 | # Meta stuff 129 | background = COLORS[3] 130 | alttext = fileName + " from " + sshIP 131 | 132 | return imageDestPath, background, alttext 133 | #### YOUR CODE HERE #### 134 | 135 | def GenerateCard(): 136 | imageFile, background, alttext = GetCardData() 137 | 138 | if not imageFile == "" and imageFile: 139 | try: 140 | printC("Opening image..." , "blue") 141 | icon = Image.open(imageFile) 142 | icon = ImageOps.exif_transpose(icon) 143 | except: 144 | import traceback 145 | logError("Unable to open image! Check the traceback.", traceback.format_exc(), sourcename) 146 | return None, None, None, None 147 | else: 148 | printC("No image! Sending null data.", "red") 149 | return None, None, None, None 150 | 151 | vertical = False 152 | if icon.size[1] >= icon.size[0]: 153 | # Tall image 154 | vertical = True 155 | printC("Tall photo! " + str(icon.size[0]) + " x " + str(icon.size[1])) 156 | choice = random.randint(0,1) 157 | if choice == 0: 158 | tilesX = 2 159 | tilesY = 2 160 | elif choice == 1: 161 | tilesX = 4 162 | tilesY = 4 163 | else: 164 | # Not tall image 165 | printC("Not tall photo! " + str(icon.size[0]) + " x " + str(icon.size[1])) 166 | choice = random.randint(0,2) 167 | if choice == 0: 168 | tilesX = 2 169 | tilesY = 2 170 | elif choice == 1: 171 | tilesX = 4 172 | tilesY = 4 173 | elif choice == 2: 174 | tilesX = 4 175 | tilesY = 2 176 | 177 | printC("Card size is " + str(tilesX) + " x " + str(tilesY), "blue") 178 | 179 | dpifactor = 300 # Change this to increase card resolution. Don't go too high!!! 180 | imageresx = tilesX*dpifactor 181 | imageresy = tilesY*dpifactor 182 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 183 | imagedraw = ImageDraw.Draw(image) 184 | 185 | if imageFile and background and alttext: 186 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=background) 187 | 188 | width = round(imageresx-(dpifactor/12)) 189 | height = round(imageresy-(dpifactor/12)) 190 | 191 | printC("Resizing image to " + str(width) + "x" + str(height)) 192 | 193 | if vertical or (tilesX == 4 and tilesY == 2): 194 | # Shrink x and y down to width of viewport 195 | wpercent = (width / float(icon.size[0])) 196 | hsize = int((float(icon.size[1]) * float(wpercent))) 197 | icon = icon.resize((width, hsize)) 198 | # crop top and bottom off to height of viewport (centered) 199 | 200 | else: 201 | # Shrink x and y down to height of viewport 202 | hpercent = (height / float(icon.size[1])) 203 | wsize = int((float(icon.size[0]) * float(hpercent))) 204 | icon = icon.resize((wsize, height)) 205 | 206 | vcentertop = (icon.size[1]/2)-(height/2) 207 | vcenterbottom = (icon.size[1]/2)+(height/2) 208 | hcenterleft = (icon.size[0]/2)-(width/2) 209 | hcenterright = (icon.size[0]/2)+(width/2) 210 | 211 | printC("Vertically centered to " + str(vcentertop) + ", " + str(vcenterbottom)) 212 | icon = icon.crop((hcenterleft, vcentertop, hcenterright, vcenterbottom)) 213 | 214 | try: 215 | image.paste(icon, (round(dpifactor/25), round(dpifactor/25)), mask=icon) 216 | except: 217 | printC("Error with transparency! Trying without...", "red") 218 | try: 219 | image.paste(icon, (round(dpifactor/25), round(dpifactor/25))) 220 | except: 221 | import traceback 222 | logError("Unable to display image! Check the traceback.", traceback.format_exc(), sourcename) 223 | return None, None, None, None 224 | 225 | try: 226 | printC("Deleting photo " + imageFile) 227 | os.remove(imageFile) 228 | except: 229 | import traceback 230 | logError("Unable to delete image! Check out the traceback!", traceback.format_exc(), sourcename) 231 | else: 232 | printC("No data! Sending null data.", "red") 233 | return None, None, None, None 234 | 235 | return image, alttext, tilesX, tilesY 236 | 237 | 238 | def printC(string, color = "white"): 239 | from termcolor import colored 240 | print(sourcename + " | " + colored(str(string), color)) 241 | 242 | def GetPresets(): 243 | # Set presets 244 | printC("Getting deps...", "blue") 245 | currentLocation = os.getcwd().replace('\\', '/') 246 | nextindex = currentLocation.rfind("/") 247 | global SMARTFRAMEFOLDER 248 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 249 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 250 | else: 251 | SMARTFRAMEFOLDER = currentLocation 252 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 253 | 254 | sys.path.append(SMARTFRAMEFOLDER) 255 | 256 | printC("Gathering colors...", "blue") 257 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 258 | colorfileLines = colorfile.readlines() 259 | global COLORS 260 | for line in colorfileLines: 261 | if "#" in line: 262 | break 263 | else: 264 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 265 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 266 | 267 | GetPresets() 268 | from ErrorLogger import logError 269 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 270 | def GetCard(): 271 | 272 | # Generate card... 273 | printC("Starting card generation...", "blue") 274 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 275 | 276 | # Check if card exists 277 | if image and alttext and tilesX and tilesY: 278 | printC("Finished generating card!...", "green") 279 | 280 | 281 | # Setup output location 282 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 283 | printC("Will output to " + outputLocation, "cyan") 284 | 285 | # Save 286 | image.save(outputLocation) 287 | printC("Image saved to " + outputLocation + "!", "green") 288 | 289 | from Card import Card 290 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 291 | else: 292 | # No cards 293 | printC("No cards to return!...", "red") 294 | return None 295 | 296 | if __name__ == "__main__": 297 | GetCard() 298 | -------------------------------------------------------------------------------- /Available Plugins/SMB Storage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SMB Storage plugin for SmartFrame 3 | # Built using the SingleRadialProgress template 4 | 5 | # This plugin will display the storage capacity of a Linux server on your network. 6 | # It runs the command df | grep 'srvrPath' and 7 | # To display the storage for multiple servers/shares, just duplicate this file in your plugins folder and put the appropriate info into the duplicate! 8 | 9 | # Required deps: Pillow, termcolor, paramiko 10 | 11 | srvrPath = "" 12 | sshUsername = "" 13 | sshPWD = "" 14 | sshIP = "192.168.1.133" 15 | sourcename = "SMB Share " 16 | 17 | # This plugin uses df to find the storage capacity of a mounted volume. 18 | 19 | from PIL import Image, ImageFont, ImageDraw 20 | import os 21 | import sys 22 | import paramiko 23 | 24 | SMARTFRAMEFOLDER = "" 25 | COLORS = [] 26 | 27 | #### YOUR CODE HERE #### 28 | def GetCardData(): 29 | progress = 0.02 30 | maintext = srvrPath 31 | alttext = str(round(progress*100))+"% storage used on network server " + maintext 32 | 33 | global sourcename 34 | try: 35 | sourcename += srvrPath[:srvrPath.index('/')] 36 | except ValueError: 37 | try: 38 | sourcename += srvrPath[:srvrPath.index('\\')] 39 | except ValueError: 40 | sourcename += srvrPath 41 | 42 | ssh = paramiko.SSHClient() 43 | ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 44 | printC("Connecting to "+sshIP+"! ", "blue") 45 | try: 46 | ssh.connect(sshIP, username=sshUsername, password=sshPWD, timeout=1) 47 | except: 48 | import traceback 49 | logError("Error connecting to server! Check the traceback.", traceback.format_exc(), sourcename) 50 | return None, None, None 51 | 52 | command = "df " + srvrPath + " | tail -1 | awk {'print substr($5, 1, length($5)-1)'}" 53 | printC("Executing " + command, "blue") 54 | try: 55 | ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command(command) 56 | ssh_stdout = ssh_stdout.read() 57 | ssh_stderr = ssh_stderr.read() 58 | except: 59 | import traceback 60 | logError("Error executing command! Check the traceback.", traceback.format_exc(), sourcename) 61 | return None, None, None 62 | 63 | try: 64 | progress = float(ssh_stdout)*0.01 65 | except: 66 | import traceback 67 | logError("Output invalid! Got " + str(ssh_stdout) + " and " + str(ssh_stderr), traceback.format_exc(), sourcename) 68 | return None, None, None 69 | 70 | return progress, maintext, alttext 71 | #### YOUR CODE HERE #### 72 | 73 | def GenerateCard(): 74 | # EDIT THESE TO CUSTOMIZE YOUR PLUGIN'S APPEARANCE! 75 | tilesX = 2 # I don't recommend changing these values for this template 76 | tilesY = 2 # I don't recommend changing these values for this template 77 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 78 | backgroundcolor = COLORS[3] # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 79 | textcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 80 | progressfillcolor = (COLORS[3][0]+50, COLORS[3][1]+50, COLORS[3][2]+50) # Change this to a 3-value tuple to change the color of your progress meter! 81 | printC("Progress bar fill color is " + str(progressfillcolor)) 82 | progressbgcolor = (COLORS[3][0]-50, COLORS[3][1]-50, COLORS[3][2]-50) # Change this to a 3-value tuple to change the background color of your progress meter! 83 | printC("Progress bar background color is " + str(progressbgcolor)) 84 | 85 | imageresx = tilesX*dpifactor 86 | imageresy = tilesY*dpifactor 87 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 88 | imagedraw = ImageDraw.Draw(image) 89 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 90 | 91 | progress, maintext, alttext = GetCardData() 92 | 93 | if maintext and alttext: 94 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 15*round(dpifactor/50)) 95 | if progress < 0.1: 96 | progresstextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 30*round(dpifactor/50)) 97 | progresstexttop = (imageresy/4)-(dpifactor/10) 98 | else: 99 | progresstextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 20*round(dpifactor/50)) 100 | progresstexttop = 5*imageresy/16 101 | imagedraw.text((dpifactor/50,3*imageresy/4), maintext, font=maintextfont, fill=textcolor) 102 | circlepos = [(imageresx/8,(imageresy/8)-(dpifactor/10)),(7*imageresx/8, (7*imageresy/8)-(dpifactor/10))] 103 | imagedraw.arc(circlepos, start=135, end=45, fill=progressbgcolor, width=round(dpifactor/4)) # Background 104 | imagedraw.arc(circlepos, start=135, end=(270*progress)+135, fill=progressfillcolor, width=round(dpifactor/4)) # Background 105 | imagedraw.text(((imageresx/4)+(dpifactor/10), progresstexttop), str(round(progress*100))+"%", font=progresstextfont, fill=textcolor) 106 | else: 107 | printC("No data! Sending null data.", "red") 108 | return None, None, None, None 109 | 110 | return image, alttext, tilesX, tilesY 111 | 112 | 113 | 114 | def printC(string, color = "white"): 115 | from termcolor import colored 116 | print(sourcename + " | " + colored(str(string), color)) 117 | 118 | def GetPresets(): 119 | # Set presets 120 | printC("Getting deps...", "blue") 121 | currentLocation = os.getcwd().replace('\\', '/') 122 | nextindex = currentLocation.rfind("/") 123 | global SMARTFRAMEFOLDER 124 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 125 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 126 | else: 127 | SMARTFRAMEFOLDER = currentLocation 128 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 129 | 130 | sys.path.append(SMARTFRAMEFOLDER) 131 | 132 | printC("Gathering colors...", "blue") 133 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 134 | colorfileLines = colorfile.readlines() 135 | global COLORS 136 | for line in colorfileLines: 137 | if "#" in line: 138 | break 139 | else: 140 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 141 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 142 | 143 | GetPresets() 144 | from ErrorLogger import logError 145 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 146 | def GetCard(): 147 | 148 | # Generate card... 149 | printC("Starting card generation...", "blue") 150 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 151 | 152 | # Check if card exists 153 | if image and alttext and tilesX and tilesY: 154 | printC("Finished generating card!...", "green") 155 | 156 | 157 | # Setup output location 158 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 159 | printC("Will output to " + outputLocation, "cyan") 160 | 161 | # Save 162 | image.save(outputLocation) 163 | printC("Image saved to " + outputLocation + "!", "green") 164 | 165 | from Card import Card 166 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 167 | else: 168 | # No cards 169 | printC("No cards to return!...", "red") 170 | return None 171 | 172 | if __name__ == "__main__": 173 | GetCard() 174 | -------------------------------------------------------------------------------- /Available Plugins/Spotify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- Spotify.py 3 | # This plugin grabs information about a song that's currently playing on your Spotify account. 4 | 5 | # Required deps: Pillow, termcolor, colorgram.py, spotipy, wget 6 | 7 | # IMPORTANT SETUP: 8 | # Spotify's API requires user authentication. Please fill the following variables: 9 | # spotifyUsername = your account username 10 | # spotifyClientID / spotifyClientSecret -- You need to make an application in Spotify's developer console. https://developer.spotify.com/dashboard 11 | # redirect_uri -- If you know what you're doing (let me know how to make this better! open an issue on github!), go ahead and change this. 12 | # Otherwise, copy the value within the "quotes" to your application in the Spotify dashboard. 13 | 14 | # Make sure you run SmartFrame in the FOREGROUND when you run this plugin for the first time. 15 | # This application will open a web browser where you need to log into your Spotify account. 16 | # IF YOU ARE RUNNING HEADLESS: you need to pipe an X session to your local machine with a web browser. 17 | #I know this is a stupid solution, so if you know how to improve this, PLEASE LET ME KNOW (create an issue on Github or join https://discord.gg/9Ms4bFw)! 18 | 19 | sourcename = "Spotify" 20 | 21 | from PIL import Image, ImageFont, ImageDraw 22 | import os 23 | import sys 24 | import colorgram 25 | import spotipy 26 | from spotipy.oauth2 import SpotifyOAuth 27 | 28 | SMARTFRAMEFOLDER = "" 29 | COLORS = [] 30 | 31 | spotifyUsername = "" 32 | spotifyClientID = "" # Get yours at https://developer.spotify.com/dashboard 33 | spotifyClientSecret = "" 34 | redirect_uri = "http://127.0.0.1:9090" # Set this to your URI in the Spotify dashboard 35 | 36 | 37 | ### YOUR CODE HERE ### 38 | def GetCardData(): 39 | 40 | def GetPathWithinNeighbouringFolder(fileWithin, folder): 41 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 42 | index = file.rfind("/") 43 | file = file[:index] 44 | fullImagePath = file + "/" + folder + "/" + fileWithin # File location of image 45 | return fullImagePath 46 | 47 | progress = 0.1 # Float between 0 and 1 48 | songname = "" 49 | artistname = "" 50 | albumArtLocation = GetPathWithinNeighbouringFolder("", "") 51 | othertext = "Your App Name\nStatus" 52 | alttext = "Whatever you want!" 53 | 54 | # Authenticate and get data 55 | try: 56 | scope = "user-read-playback-state" 57 | oauth = SpotifyOAuth(scope=scope, redirect_uri=redirect_uri, cache_path=__file__+".spotifcache", client_id=spotifyClientID, client_secret=spotifyClientSecret) 58 | spotify = spotipy.Spotify(auth_manager=oauth) 59 | printC("Connecting to Spotify...", "blue") 60 | current_track = spotify.current_playback(additional_types="episode") 61 | except: 62 | import traceback 63 | logError("Error getting Spotify data! Returning null card...", traceback.format_exc(), sourcename) 64 | return None, None, None, None, None, None 65 | 66 | 67 | # Parse data 68 | if not current_track or not current_track['is_playing']: 69 | # Not playing 70 | printC("Spotify is not playing anything.", "yellow") 71 | return None, None, None, None, None, None 72 | else: 73 | url = "" 74 | 75 | # Name 76 | songname = current_track['item']['name'] 77 | 78 | # Progress 79 | progress = current_track['progress_ms']/current_track['item']['duration_ms'] 80 | 81 | # Device name 82 | devicename = "" 83 | for device in spotify.devices()['devices']: 84 | if device['is_active']: 85 | devicename = device['name'] 86 | 87 | othertext = "Spotify | On " + devicename 88 | 89 | if current_track['item']['type'] == "episode": # Podcast 90 | printC("Podcast!") 91 | artistname = current_track['item']['show']['name'] 92 | url = current_track['item']['show']['images'][1]['url'] 93 | 94 | else: # Song 95 | # Artist name 96 | for artist in current_track['item']['artists'][:-1]: 97 | artistname += artist['name'] + ", " 98 | artistname += current_track['item']['artists'][-1]['name'] 99 | 100 | # Playlist name 101 | try: 102 | current_list = spotify.user_playlist(user="RaddedMC", playlist_id=current_track["context"]["uri"], fields="name")['name'] 103 | printC("Playlist!") 104 | except: 105 | current_list = "" 106 | # No specific playlist 107 | 108 | othertext += "\n" + current_list 109 | 110 | url = current_track['item']['album']['images'][1]['url'] 111 | 112 | import wget 113 | try: 114 | albumArtLocation = wget.download(url,__file__+".image.jpg") 115 | printC("Downloaded album art! Saved to " + albumArtLocation, "green") 116 | except: 117 | printC("Failed to download album art!", "red") 118 | import traceback 119 | traceback.print_exc() 120 | 121 | alttext = "Spotify is playing " + songname + " by " + artistname 122 | return progress, songname, artistname, albumArtLocation, othertext, alttext 123 | ### YOUR CODE HERE ### 124 | 125 | 126 | 127 | def GenerateCard(): 128 | tilesX = 2 # Change this to change tile size 129 | tilesY = 2 # Change this to change tile size 130 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 131 | backgroundOverrideColor = COLORS[3] # This color will be displayed in the background if album art can't be found or displayed. 132 | songtextcolor = COLORS[0] 133 | artisttextcolor = COLORS[1] 134 | songtextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 12*round(dpifactor/50)) 135 | artisttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 10*round(dpifactor/50)) 136 | othertextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 7*round(dpifactor/50)) 137 | 138 | imageresx = tilesX*dpifactor 139 | imageresy = tilesY*dpifactor 140 | image = Image.new("RGBA", (tilesX*dpifactor, tilesY*dpifactor)) 141 | 142 | padding = round(dpifactor/25) 143 | 144 | progress, songname, artistname, albumArtLocation, othertext, alttext = GetCardData() 145 | 146 | if songname: 147 | 148 | deleteimage = True 149 | 150 | try: 151 | printC("Running colorgram...", "blue") 152 | albumartcolour = colorgram.extract(albumArtLocation, 1)[0].rgb 153 | printC("Colorgram done!", "green") 154 | 155 | # Tune colours 156 | import colorsys 157 | albumartcolourhsv = colorsys.rgb_to_hsv(albumartcolour[0]/255, albumartcolour[1]/255, albumartcolour[2]/255) 158 | if albumartcolourhsv[2] > 0.9: 159 | printC("Superbright!!") 160 | albumartcolour = (albumartcolour[0]-200, albumartcolour[1]-200, albumartcolour[2]-200) 161 | progressbarcolor = (albumartcolour[0]+150, albumartcolour[1]+150, albumartcolour[2]+150) 162 | transparency = 220 163 | elif albumartcolourhsv[2] > 0.6: 164 | printC("Bright!!") 165 | albumartcolour = (albumartcolour[0]-100, albumartcolour[1]-100, albumartcolour[2]-100) 166 | progressbarcolor = (albumartcolour[0]+150, albumartcolour[1]+150, albumartcolour[2]+150) 167 | transparency = 180 168 | elif albumartcolourhsv[2] < 0.2: 169 | printC("Dark!!") 170 | albumartcolour = (albumartcolour[0], albumartcolour[1], albumartcolour[2]) 171 | progressbarcolor = (albumartcolour[0]+175, albumartcolour[1]+175, albumartcolour[2]+175) 172 | transparency = 150 173 | else: 174 | printC("Normal!!") 175 | progressbarcolor = (albumartcolour[0]+100, albumartcolour[1]+100, albumartcolour[2]+100) 176 | transparency = 100 177 | 178 | # Get album art 179 | albumart = Image.open(albumArtLocation) 180 | albumart = albumart.resize((imageresx, imageresy)) 181 | image.paste(albumart, (0, 0)) 182 | 183 | # Background darken overlay thing 184 | overlay = Image.new("RGBA", (imageresx, imageresy)) 185 | overlayDraw = ImageDraw.Draw(overlay) 186 | overlayDraw.rectangle([(0,0), (imageresx, imageresy)], fill=(albumartcolour[0], albumartcolour[1], albumartcolour[2], transparency)) # Semitransparent overlay for text contrast 187 | image = Image.alpha_composite(image, overlay) 188 | 189 | except: 190 | imagedraw = ImageDraw.Draw(image) 191 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundOverrideColor) # Background if it can't get an image 192 | progressbarcolor = (backgroundOverrideColor[0]+50, backgroundOverrideColor[1]+50, backgroundOverrideColor[2]+50) 193 | import traceback 194 | logError("Unable to display album art or set progress bar color! Check out the traceback!", traceback.format_exc(), sourcename) 195 | deleteimage = False 196 | 197 | imagedraw = ImageDraw.Draw(image) 198 | 199 | if deleteimage: 200 | try: 201 | printC("Deleting album art image " + albumArtLocation) 202 | os.remove(albumArtLocation) 203 | except: 204 | import traceback 205 | logError("Unable to delete album art image! Check out the traceback!", traceback.format_exc(), sourcename) 206 | 207 | 208 | imagedraw.text((padding, padding*2), songname, font=songtextfont, fill=songtextcolor) # Song name 209 | imagedraw.text((padding, (padding*2)+round(dpifactor/4)), artistname, font=artisttextfont, fill=artisttextcolor) # Artist name 210 | imagedraw.text((padding, imageresy-round(5*dpifactor/12)), othertext, font=othertextfont, fill=artisttextcolor) # App name 211 | overlay = Image.new("RGBA", (imageresx, imageresy)) 212 | overlayDraw = ImageDraw.Draw(overlay) 213 | overlayDraw.rounded_rectangle([(padding, round(2*imageresy/3)), (imageresx-padding, round(2*imageresy/3)+round(4*padding))], fill=(255,255,255,50), radius=round(dpifactor/5)) # Progress meter BG 214 | image = Image.alpha_composite(image, overlay) 215 | imagedraw = ImageDraw.Draw(image) 216 | if progress >= 0.1: 217 | imagedraw.rounded_rectangle([(padding, round(2*imageresy/3)), (progress*(imageresx-padding), round(2*imageresy/3)+round(4*padding))], fill=progressbarcolor, radius=round(dpifactor/5)) # Progress meter FG 218 | 219 | else: 220 | printC("No data! Sending null data.", "red") 221 | return None, None, None, None 222 | 223 | return image, alttext, tilesX, tilesY 224 | 225 | 226 | def printC(string, color = "white"): 227 | from termcolor import colored 228 | print(sourcename + " | " + colored(str(string), color)) 229 | 230 | def GetPresets(): 231 | # Set presets 232 | printC("Getting deps...", "blue") 233 | currentLocation = os.getcwd().replace('\\', '/') 234 | nextindex = currentLocation.rfind("/") 235 | global SMARTFRAMEFOLDER 236 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 237 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 238 | else: 239 | SMARTFRAMEFOLDER = currentLocation 240 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 241 | 242 | sys.path.append(SMARTFRAMEFOLDER) 243 | 244 | printC("Gathering colors...", "blue") 245 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 246 | colorfileLines = colorfile.readlines() 247 | global COLORS 248 | for line in colorfileLines: 249 | if "#" in line: 250 | break 251 | else: 252 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 253 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 254 | 255 | GetPresets() 256 | from ErrorLogger import logError 257 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 258 | def GetCard(): 259 | 260 | # Generate card... 261 | printC("Starting card generation...", "blue") 262 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 263 | 264 | # Check if card exists 265 | if image and alttext and tilesX and tilesY: 266 | printC("Finished generating card!...", "green") 267 | 268 | 269 | # Setup output location 270 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 271 | printC("Will output to " + outputLocation, "cyan") 272 | 273 | # Save 274 | image.save(outputLocation) 275 | printC("Image saved to " + outputLocation + "!", "green") 276 | 277 | from Card import Card 278 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 279 | else: 280 | # No cards 281 | printC("No cards to return!...", "red") 282 | return None 283 | 284 | if __name__ == "__main__": 285 | GetCard() 286 | 287 | -------------------------------------------------------------------------------- /Available Plugins/UnifiClients.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's Unifi plugin for SmartFrame -- UnifiClients.py 3 | 4 | # For users of Ubiquiti Unifi, this plugin will show the number of clients connected to your network through your controller. 5 | # Required: A UniFi network setup with a locally hosted controller 6 | 7 | # Required deps: Pillow, termcolor, unificontrol 8 | 9 | # Setup: Set the below variables to the IP of your controller, the port if you have changed it, and your login information. 10 | # I know storing logins in plaintext is stupid. If more interest arises I will change this! 11 | 12 | # Add any user-definable variables here! (API keys, usernames, etc.) 13 | 14 | sourcename = "Unifi Controller" 15 | 16 | from PIL import Image, ImageFont, ImageDraw 17 | import os 18 | import sys 19 | import math 20 | import unificontrol 21 | 22 | SMARTFRAMEFOLDER = "" 23 | COLORS = [] 24 | 25 | controllerHostname = "192.168.1.42" # IP address or hostname of your controller 26 | controllerPort = 8443 # Leave if you don't know this 27 | username = "" 28 | password = "" 29 | 30 | #### YOUR CODE HERE #### 31 | def GetCardData(): 32 | count = 21 33 | maintext = "Some data" 34 | alttext = "Whatever you want!" 35 | 36 | try: 37 | printC("Logging into controller...") 38 | client = unificontrol.UnifiClient(username=username, password=password, host=controllerHostname, port=controllerPort) 39 | devices = client.list_clients() 40 | count = len(devices) 41 | printC("There are " + str(count) + " clients on the network.") 42 | for site in client.list_sites(): 43 | if site['name'] == "default": 44 | maintext = site['desc'] + "\n" + "devices" 45 | printC("The site name is " + site['desc']) 46 | break 47 | maintext = "Unifi\ndevices" 48 | 49 | alttext = "There are " + str(count) + " devices on your network." 50 | except: 51 | printC("Unknown error. take a look at the traceback!", "red") 52 | import traceback 53 | logError("Unknown error. take a look at the traceback!", traceback.format_exc(), sourcename) 54 | return None, None, None 55 | 56 | 57 | return count, maintext, alttext 58 | #### YOUR CODE HERE #### 59 | 60 | def GenerateCard(): 61 | # EDIT THESE TO CUSTOMIZE YOUR PLUGIN'S APPEARANCE! 62 | tilesX = 2 # I don't recommend changing these values for this template 63 | tilesY = 2 # I don't recommend changing these values for this template 64 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 65 | backgroundcolor = (50, 50, 175) # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 66 | textcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 67 | circlesbgcolor = (40, 40, 165) # Change this to a 3-value tuple to change the background color of your progress meter! 68 | printC("Counter circles background color is " + str(circlesbgcolor)) 69 | 70 | imageresx = tilesX*dpifactor 71 | imageresy = tilesY*dpifactor 72 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 73 | imagedraw = ImageDraw.Draw(image) 74 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 75 | 76 | count, maintext, alttext = GetCardData() 77 | 78 | if maintext and alttext: 79 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 15*round(dpifactor/50)) 80 | if count < 10: # Don't worry i hate this too 81 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 82 | elif count < 20: 83 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 84 | elif count < 100: 85 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 86 | elif count < 1000: 87 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 40*round(dpifactor/50)) 88 | elif count < 10000: 89 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 35*round(dpifactor/50)) 90 | elif count < 100000: 91 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 30*round(dpifactor/50)) 92 | else: 93 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 20*round(dpifactor/50)) 94 | 95 | # Logarithm 96 | try: 97 | logAmt = math.log((count),10) 98 | except ValueError: 99 | logAmt = 1 100 | if logAmt == 0: 101 | logAmt = 1 102 | printC("LogAmt is " + str(logAmt)) 103 | 104 | # Top position 105 | topdivamt = (128/(5*logAmt)) 106 | if topdivamt < 3: 107 | topdivamt = 3 108 | counttexttop = imageresx/topdivamt 109 | 110 | # Start position 111 | startdivamt = (2.2*logAmt)+3 112 | if count < 10: 113 | startdivamt = 3 114 | counttextstart = imageresx/startdivamt 115 | 116 | printC("Counter scale factors are (" + str(startdivamt) + ", " + str(topdivamt) + ")") 117 | 118 | # Circles 119 | if not count == 0: 120 | cols = math.ceil(math.sqrt(count)) 121 | rows = round(math.sqrt(count)) 122 | printC("Generating a ("+str(cols)+"x"+str(rows)+") grid of " + str(count) + " circles...") 123 | 124 | padding = imageresx/(4*cols) 125 | size = (imageresx/cols) - padding 126 | for i in range(0,count): 127 | col = i % cols 128 | row = math.floor(i/cols) 129 | xpos = (padding/2)+(size+padding)*col 130 | ypos = (padding/2)+(size+padding)*row 131 | imagedraw.ellipse((xpos, ypos, xpos+size, ypos+size), fill=circlesbgcolor) 132 | 133 | imagedraw.text((dpifactor/50,5*imageresy/8), maintext, font=maintextfont, fill=textcolor) 134 | imagedraw.text((counttextstart, counttexttop), str(count), font=counttextfont, fill=textcolor) # Counter text 135 | else: 136 | printC("No data! Sending null data.", "red") 137 | return None, None, None, None 138 | 139 | return image, alttext, tilesX, tilesY 140 | 141 | def printC(string, color = "white"): 142 | from termcolor import colored 143 | print(sourcename + " | " + colored(str(string), color)) 144 | 145 | def GetPresets(): 146 | # Set presets 147 | printC("Getting deps...", "blue") 148 | currentLocation = os.getcwd().replace('\\', '/') 149 | nextindex = currentLocation.rfind("/") 150 | global SMARTFRAMEFOLDER 151 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 152 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 153 | else: 154 | SMARTFRAMEFOLDER = currentLocation 155 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 156 | 157 | sys.path.append(SMARTFRAMEFOLDER) 158 | 159 | printC("Gathering colors...", "blue") 160 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 161 | colorfileLines = colorfile.readlines() 162 | global COLORS 163 | for line in colorfileLines: 164 | if "#" in line: 165 | break 166 | else: 167 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 168 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 169 | 170 | GetPresets() 171 | from ErrorLogger import logError 172 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 173 | def GetCard(): 174 | 175 | # Generate card... 176 | printC("Starting card generation...", "blue") 177 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 178 | 179 | # Check if card exists 180 | if image and alttext and tilesX and tilesY: 181 | printC("Finished generating card!...", "green") 182 | 183 | 184 | # Setup output location 185 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 186 | printC("Will output to " + outputLocation, "cyan") 187 | 188 | # Save 189 | image.save(outputLocation) 190 | printC("Image saved to " + outputLocation + "!", "green") 191 | 192 | from Card import Card 193 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 194 | else: 195 | # No cards 196 | printC("No cards to return!...", "red") 197 | return None 198 | 199 | if __name__ == "__main__": 200 | GetCard() 201 | 202 | -------------------------------------------------------------------------------- /Available Plugins/WemoKasaPlugs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- WemoKasaPlugs.py 3 | # This plugin grabs state info from Belkin WeMo and TP-Link Kasa smart plugs on your network. 4 | # No special setup is required, but if you have issues discovering Kasa plugs, set the KasaSubnet variable to your network's broadcast address! 5 | # (Usually 192.168.1.255 or 192.168.0.255 or something else ending in 255) 6 | 7 | # Required deps: Pillow, termcolor, pywemo, python-kasa 8 | 9 | sourcename = "Kasa and WeMo plugs" 10 | KasaSubnet = "192.168.1.255" 11 | 12 | from PIL import Image, ImageFont, ImageDraw 13 | import os 14 | import sys 15 | import pywemo 16 | from kasa import Discover 17 | import asyncio 18 | 19 | SMARTFRAMEFOLDER = "" 20 | COLORS = [] 21 | 22 | def GetCardData(): 23 | def GetPathWithinNeighbouringFolder(fileWithin, folder): 24 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 25 | index = file.rfind("/") 26 | file = file[:index] 27 | fullImagePath = file + "/" + folder + "/" + fileWithin # File location of image 28 | return fullImagePath 29 | 30 | itemList = [] 31 | maintext = "Kasa | WeMo" 32 | alttext = "Whatever you want!" # Number of plugs 33 | 34 | plugicon = GetPathWithinNeighbouringFolder("outlet.png", "WemoKasaPlugs") 35 | 36 | try: 37 | printC("Getting Wemo Plugs...", "blue") 38 | wemoplugs = pywemo.discover_devices() 39 | for wemoplug in wemoplugs: 40 | try: 41 | plugname = wemoplug.basicevent.GetFriendlyName()['FriendlyName'] 42 | plugstate = wemoplug.get_state() 43 | if not plugstate: 44 | printC("Plug " + plugname + " is off! Skipping...") 45 | continue 46 | plugcolor = (66, 245, 117) 47 | itemList.append(Item(plugname, plugicon, plugcolor, bgFillAmt=1)) 48 | except: 49 | import traceback 50 | logError("Unable to get info from a wemo plug!", traceback.format_exc(), sourcename) 51 | continue 52 | except: 53 | import traceback 54 | logError("Unknown error with WeMo plugs!", traceback.format_exc(), sourcename) 55 | 56 | try: 57 | printC("Getting Kasa Plugs...", "blue") 58 | if not KasaSubnet or KasaSubnet == "": 59 | kasaplugs = asyncio.run(Discover.discover()) 60 | else: 61 | kasaplugs = asyncio.run(Discover.discover(target=KasaSubnet)) 62 | for plugIP in kasaplugs: 63 | try: 64 | plugname = kasaplugs[plugIP].alias 65 | plugstate = kasaplugs[plugIP].is_on 66 | if not plugstate: 67 | printC("Plug " + plugname + " is off! Skipping...") 68 | continue 69 | plugcolor = (66, 245, 182) # True 70 | itemList.append(Item(plugname, plugicon, plugcolor, bgFillAmt=1)) 71 | except: 72 | import traceback 73 | logError("Unable to get info from a kasa plug!", traceback.format_exc(), sourcename) 74 | continue 75 | except: 76 | import traceback 77 | logError("Unknown error with Kasa plugs!", traceback.format_exc(), sourcename) 78 | 79 | printC("Data collect done!", "green") 80 | 81 | return itemList, maintext, alttext 82 | 83 | 84 | def GenerateCard(): 85 | 86 | itemList, maintext, alttext = GetCardData() 87 | if itemList: 88 | if len(itemList) > 8: 89 | tilesX = 4 90 | tilesY = 4 91 | elif len(itemList) > 4: 92 | tilesX = 4 93 | tilesY = 2 94 | else: 95 | tilesX = 2 96 | tilesY = 2 97 | 98 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 99 | imageresx = tilesX*dpifactor 100 | imageresy = tilesY*dpifactor 101 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 102 | imagedraw = ImageDraw.Draw(image) 103 | 104 | # Draw background 105 | backgroundcolor = (189, 255, 205) # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 106 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 107 | 108 | # Draw Item tiles 109 | padding = dpifactor/50 110 | xcount = 0 111 | ycount = 0 112 | for item in itemList: 113 | printC("Getting image for item " + item.itemName) 114 | try: 115 | itemImg = item.Image(round(dpifactor-(2*padding)-round(dpifactor/8)), round(dpifactor/6)) 116 | except: 117 | import traceback 118 | logError("Unknown error with image " + item.itemName + "! Moving on to next image...", traceback.format_exc(), sourcename) 119 | continue 120 | image.paste(itemImg, (round((dpifactor/(16/tilesX))+padding+(xcount*(dpifactor-(dpifactor/8)))), round(padding+(ycount*(dpifactor-(dpifactor/8))))), mask=itemImg) 121 | if xcount+1 == tilesX: 122 | xcount = 0 123 | ycount += 1 124 | else: 125 | xcount += 1 126 | 127 | # Draw maintext 128 | maintextcolor = (0,0,0) # Change this to a 3-value tuple to change the text colour! 129 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 12*round(dpifactor/50)) 130 | imagedraw.text((round(padding), round(imageresy-(dpifactor/3))), maintext, fill=maintextcolor, font=maintextfont) 131 | else: 132 | printC("No data! Sending null data.", "red") 133 | return None, None, None, None 134 | 135 | return image, alttext, tilesX, tilesY 136 | 137 | 138 | # Item class in ObjectGroup 139 | class Item: 140 | itemName = "" 141 | iconLocation = "" # Icon file path 142 | bgColor = (0,0,0) # Background color of item 143 | bgFillAmt = 1 # Percentage (0 - 1) of background filledness, useful to show smart light brightness 144 | 145 | def __init__(self, itemName, iconLocation, bgColor, bgFillAmt=1): 146 | self.itemName = itemName 147 | self.iconLocation = iconLocation 148 | self.bgColor = bgColor 149 | self.bgFillAmt = bgFillAmt 150 | print("New Item | " + self.itemName + " with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor)) 151 | 152 | # Returns a pretty Item with a rounded-rectangle background 153 | def Image(self, xyres, cornerrad): 154 | 155 | # Create the image of provided size 156 | image = Image.new(mode="RGBA", size = (xyres, xyres)) 157 | imagedraw = ImageDraw.Draw(image) 158 | 159 | # This background changes height based on the fill amount. Useful for smart light brightness or speaker volume. 160 | imagedraw.rounded_rectangle([(0,0),(xyres,xyres)], fill=(255,255,255,100), radius=cornerrad) # BG 161 | imagedraw.rounded_rectangle([(0,round(xyres*(1-self.bgFillAmt))),(xyres,xyres)], fill=(round(self.bgColor[0]), round(self.bgColor[1]), round(self.bgColor[2])), radius=round(cornerrad)) # FG 162 | 163 | # Overlay the icon 164 | icon = Image.open(self.iconLocation) 165 | icon = icon.resize((round(xyres-(xyres/3)), round(xyres-(xyres/3)))) 166 | image.paste(icon, (round(xyres/6), round(xyres/10)), mask=icon) 167 | 168 | # Add itemname text 169 | itemtextcolor = (0,0,0) # To change the appearance of maintext font, backgrounds, or anything else, head to GenerateCard()! 170 | itemtextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", round(xyres/8)) 171 | imagedraw.text((cornerrad/2, 4*xyres/5), self.itemName[:11], fill=itemtextcolor, font=itemtextfont) 172 | 173 | return image 174 | 175 | def __str__(self): 176 | return "Item object: " + self.itemName + "' with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor) 177 | 178 | def printC(string, color = "white"): 179 | from termcolor import colored 180 | print(sourcename + " | " + colored(str(string), color)) 181 | 182 | def GetPresets(): 183 | # Set presets 184 | printC("Getting deps...", "blue") 185 | currentLocation = os.getcwd().replace('\\', '/') 186 | nextindex = currentLocation.rfind("/") 187 | global SMARTFRAMEFOLDER 188 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 189 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 190 | else: 191 | SMARTFRAMEFOLDER = currentLocation 192 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 193 | 194 | sys.path.append(SMARTFRAMEFOLDER) 195 | 196 | printC("Gathering colors...", "blue") 197 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 198 | colorfileLines = colorfile.readlines() 199 | global COLORS 200 | for line in colorfileLines: 201 | if "#" in line: 202 | break 203 | else: 204 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 205 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 206 | 207 | GetPresets() 208 | from ErrorLogger import logError 209 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 210 | def GetCard(): 211 | 212 | # Generate card... 213 | printC("Starting card generation...", "blue") 214 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 215 | # Check if card exists 216 | if image and alttext and tilesX and tilesY: 217 | printC("Finished generating card!...", "green") 218 | 219 | 220 | # Setup output location 221 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 222 | printC("Will output to " + outputLocation, "cyan") 223 | 224 | # Save 225 | image.save(outputLocation) 226 | printC("Image saved to " + outputLocation + "!", "green") 227 | 228 | from Card import Card 229 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 230 | else: 231 | # No cards 232 | printC("No cards to return!...", "red") 233 | return None 234 | 235 | if __name__ == "__main__": 236 | GetCard() 237 | -------------------------------------------------------------------------------- /Available Plugins/WemoKasaPlugs/Attributions.md: -------------------------------------------------------------------------------- 1 | Plug icon is from https://cdn.icon-icons.com/icons2/510/PNG/512/outlet_icon-icons.com_50081.png -------------------------------------------------------------------------------- /Available Plugins/WemoKasaPlugs/outlet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Available Plugins/WemoKasaPlugs/outlet.png -------------------------------------------------------------------------------- /Available Plugins/YouTubeSubs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's YouTube Subscriber counter plugin for SmartFrame 3 | # Built using the SingleNumber template 4 | 5 | # Required deps: Pillow, termcolor, urllib 6 | 7 | # This plugin will display the number of subscribers of a specific YouTube channel. You must supply a channel ID. 8 | 9 | # Add any user-definable variables here! (API keys, usernames, etc.) 10 | sourcename = "YouTube subscriber count" 11 | 12 | channelID = "UCM2eCQho7OrnnIRDDcHjWWQ" # Do not replace this with a channel name! Use a website like https://commentpicker.com/youtube-channel-id.php 13 | ytapiKEY = "AIzaSyBTmuXopw-_1JzdHF66fjQJILrFih724Po" # For now, feel free to use my API key. If you want to get your own, go to https://developers.google.com/youtube/v3/getting-started 14 | # Don't worry about any instructions related to OAuth token generation. 15 | 16 | from PIL import Image, ImageFont, ImageDraw 17 | import os 18 | import sys 19 | import math 20 | 21 | SMARTFRAMEFOLDER = "" 22 | COLORS = [] 23 | 24 | #### YOUR CODE HERE #### 25 | def GetCardData(): 26 | count = 21 27 | maintext = "" 28 | alttext = "" 29 | 30 | # Code inspired by https://github.com/howCodeORG/Python-Sub-Count/blob/master/subs.py 31 | import urllib.request 32 | import json 33 | 34 | statdata = urllib.request.urlopen("https://www.googleapis.com/youtube/v3/channels?part=statistics&id="+channelID+"&key="+ytapiKEY).read() 35 | namedata = urllib.request.urlopen("https://www.googleapis.com/youtube/v3/channels?part=brandingSettings&id="+channelID+"&key="+ytapiKEY).read() 36 | subs = json.loads(statdata)["items"][0]["statistics"]["subscriberCount"] 37 | name = json.loads(namedata)["items"][0]["brandingSettings"]["channel"]["title"] 38 | count = int(subs) 39 | maintext = "Subscribers\n"+name 40 | alttext = "Channel " + name + " has " + str(subs) + " subscribers!" 41 | 42 | return count, maintext, alttext 43 | #### YOUR CODE HERE #### 44 | 45 | def GenerateCard(): 46 | # EDIT THESE TO CUSTOMIZE YOUR PLUGIN'S APPEARANCE! 47 | tilesX = 2 # I don't recommend changing these values for this template 48 | tilesY = 2 # I don't recommend changing these values for this template 49 | COLORS[3] = (150, 100, 100) 50 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 51 | backgroundcolor = COLORS[3] # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 52 | textcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 53 | circlesbgcolor = (COLORS[3][0]-10, COLORS[3][1]-10, COLORS[3][2]-10) # Change this to a 3-value tuple to change the background color of your progress meter! 54 | printC("Counter circles background color is " + str(circlesbgcolor)) 55 | 56 | imageresx = tilesX*dpifactor 57 | imageresy = tilesY*dpifactor 58 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 59 | imagedraw = ImageDraw.Draw(image) 60 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 61 | 62 | count, maintext, alttext = GetCardData() 63 | 64 | if maintext and alttext: 65 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 15*round(dpifactor/50)) 66 | if count < 10: # Don't worry i hate this too 67 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 68 | elif count < 20: 69 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 70 | elif count < 100: 71 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 72 | elif count < 1000: 73 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 40*round(dpifactor/50)) 74 | elif count < 10000: 75 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 35*round(dpifactor/50)) 76 | elif count < 100000: 77 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 30*round(dpifactor/50)) 78 | else: 79 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 20*round(dpifactor/50)) 80 | 81 | # Logarithm 82 | try: 83 | logAmt = math.log((count),10) 84 | except ValueError: 85 | logAmt = 1 86 | if logAmt == 0: 87 | logAmt = 1 88 | printC("LogAmt is " + str(logAmt)) 89 | 90 | # Top position 91 | topdivamt = (128/(5*logAmt)) 92 | if topdivamt < 3: 93 | topdivamt = 3 94 | counttexttop = imageresx/topdivamt 95 | 96 | # Start position 97 | startdivamt = (2.2*logAmt)+3 98 | if count < 10: 99 | startdivamt = 3 100 | counttextstart = imageresx/startdivamt 101 | 102 | printC("Counter scale factors are (" + str(startdivamt) + ", " + str(topdivamt) + ")") 103 | 104 | # Circles 105 | if not count == 0: 106 | cols = math.ceil(math.sqrt(count)) 107 | rows = round(math.sqrt(count)) 108 | printC("Generating a ("+str(cols)+"x"+str(rows)+") grid of " + str(count) + " circles...") 109 | 110 | padding = imageresx/(4*cols) 111 | size = (imageresx/cols) - padding 112 | for i in range(0,count): 113 | col = i % cols 114 | row = math.floor(i/cols) 115 | xpos = (padding/2)+(size+padding)*col 116 | ypos = (padding/2)+(size+padding)*row 117 | imagedraw.ellipse((xpos, ypos, xpos+size, ypos+size), fill=circlesbgcolor) 118 | 119 | imagedraw.text((dpifactor/50,5*imageresy/8), maintext, font=maintextfont, fill=textcolor) 120 | imagedraw.text((counttextstart, counttexttop), str(count), font=counttextfont, fill=textcolor) # Counter text 121 | else: 122 | printC("No data! Sending null data.", "red") 123 | return None, None, None, None 124 | 125 | return image, alttext, tilesX, tilesY 126 | 127 | 128 | 129 | def printC(string, color = "white"): 130 | from termcolor import colored 131 | print(sourcename + " | " + colored(str(string), color)) 132 | 133 | def GetPresets(): 134 | # Set presets 135 | printC("Getting deps...", "blue") 136 | currentLocation = os.getcwd().replace('\\', '/') 137 | nextindex = currentLocation.rfind("/") 138 | global SMARTFRAMEFOLDER 139 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 140 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 141 | else: 142 | SMARTFRAMEFOLDER = currentLocation 143 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 144 | 145 | sys.path.append(SMARTFRAMEFOLDER) 146 | 147 | printC("Gathering colors...", "blue") 148 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 149 | colorfileLines = colorfile.readlines() 150 | global COLORS 151 | for line in colorfileLines: 152 | if "#" in line: 153 | break 154 | else: 155 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 156 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 157 | 158 | GetPresets() 159 | from ErrorLogger import logError 160 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 161 | def GetCard(): 162 | 163 | # Generate card... 164 | printC("Starting card generation...", "blue") 165 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 166 | 167 | # Check if card exists 168 | if image and alttext and tilesX and tilesY: 169 | printC("Finished generating card!...", "green") 170 | 171 | 172 | # Setup output location 173 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 174 | printC("Will output to " + outputLocation, "cyan") 175 | 176 | # Save 177 | image.save(outputLocation) 178 | printC("Image saved to " + outputLocation + "!", "green") 179 | 180 | from Card import Card 181 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 182 | else: 183 | # No cards 184 | printC("No cards to return!...", "red") 185 | return None 186 | 187 | if __name__ == "__main__": 188 | GetCard() 189 | -------------------------------------------------------------------------------- /Card.py: -------------------------------------------------------------------------------- 1 | # RaddedMC's SmartFrame v2 -- Card.py 2 | 3 | # fileLocation -- The location of the image attached to this object. Used in Image() 4 | # JUST GIVE THE IMAGE's FILENAME. Card.Image() will do all the heavy lifting. 5 | # alttext -- Alternate text for the image that contains basic data. 6 | # sourcename -- The name of the source that generated the Card. 7 | # Image() -- returns a PIL image generated from fileLocation. 8 | # tilesX -- returns width of image in tile units. (200 pixels per unit) 9 | # tilesY -- returns height of image in tile units. 10 | 11 | # Required deps: Pillow 12 | 13 | # KEY FILES: Cards/ 14 | 15 | # Recommended card sizes: 16 | # -- 1x1, single icon 17 | # -- 2x2, icon and some text 18 | # -- 4x2, long text 19 | # -- 4x4, a lotta data 20 | 21 | 22 | class Card: 23 | 24 | fileLocation = "" 25 | alttext = "" 26 | sourcename = "" 27 | tilesx = 2 28 | tilesy = 2 29 | 30 | def __init__(self, fileLocation, alttext, sourcename, tilesx, tilesy): 31 | self.fileLocation = fileLocation 32 | self.alttext = alttext 33 | self.sourcename = sourcename 34 | self.tilesx = tilesx 35 | self.tilesy = tilesy 36 | print("New Card | " + sourcename + "'s card at " + fileLocation + " | Size: " + str(tilesx) + "x"+str(tilesy) + " | " + alttext) 37 | 38 | def __str__(self): 39 | return "Card object: " + self.sourcename + "'s card at " + self.fileLocation + " | Size: " + str(self.tilesx) + "x"+str(self.tilesy) + " | " + self.alttext 40 | 41 | def Image(self, sizex=200, sizey=200): 42 | from PIL import Image 43 | import os 44 | imgdir = self.fileLocation 45 | image = Image.open(imgdir) 46 | image = image.resize((round(sizex), round(sizey))) 47 | return image -------------------------------------------------------------------------------- /Cards/placed here to stop git from deleting it bad git: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Cards/placed here to stop git from deleting it bad git -------------------------------------------------------------------------------- /Colors.txt: -------------------------------------------------------------------------------- 1 | 255 255 255 2 | 200 200 200 3 | 225 225 225 4 | 100 100 125 5 | # 6 | # Color 1 = primary -- used for date text 7 | # Color 2 = secondary -- used for clock text 8 | # Color 3 = tertiary -- used for any extra text 9 | # Color 4 = background -- used for background of date/time plugin 10 | 11 | # Replace these colors with your theme colors in RGB. Format your numbers with zero-padding so that there are always 11 characters (e.g. 073 208 004). 12 | 13 | # These colours only change the default date and time plugin; other colours can be configured individually for each plugin in their respective configuration files or folder. 14 | -------------------------------------------------------------------------------- /ErrorLogger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- ErrorLogger.py 3 | # Logs errors from other files 4 | 5 | from termcolor import colored 6 | from datetime import datetime 7 | 8 | def getLogLocation(): 9 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 10 | index = file.rfind("/") 11 | file = file[:index] 12 | logFilePath = file + "/" + "SmartFrame.Error.Log.TXT" # File location of image 13 | return logFilePath 14 | 15 | def logError(errorHeader, traceback, moduleName): 16 | print(moduleName + " | " + colored(str(errorHeader), "red")) 17 | print(traceback) 18 | try: 19 | with open(getLogLocation(), "a") as logfile: 20 | now = datetime.now() 21 | logfile.write(moduleName + " @ " + now.strftime("%H:%M:%S %d/%m/%Y") + " | " + errorHeader + "\n") 22 | logfile.write(traceback + "\n") 23 | except: 24 | print("Error Logger | " + colored(str("Error writing log to file!"), "red")) 25 | import traceback 26 | traceback.print_exc() -------------------------------------------------------------------------------- /Fonts/font1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Fonts/font1.ttf -------------------------------------------------------------------------------- /GenerateImage.py: -------------------------------------------------------------------------------- 1 | # RaddedMC's SmartFrame v2 -- GenerateImage.py 2 | # This program generates an image that is to be sent to the display source. 3 | 4 | # GenerateImage(cardsinstance, resx, resy, scale) 5 | # cardsinstance -- an array of cards, this will be copied and your original variable will not be changed 6 | # resx & resy -- X and Y resolution 7 | # scale -- an integer to adjust the size of everything, larger value = smaller text and more cards 8 | 9 | # printG() -- just a simple output formatter 10 | 11 | # Required deps: Pillow, Card, termcolor 12 | # KEY FILES: Fonts/font1.ttf 13 | # Colors.txt 14 | 15 | from ErrorLogger import logError 16 | 17 | moduleName = "Image Generator" 18 | 19 | def printG(string, color = "white"): 20 | from termcolor import colored 21 | print("Image Generator | " + colored(str(string), color)) 22 | 23 | def GenerateImage(cardsinstance, resx, resy, scale): 24 | try: 25 | # Imports and key variables 26 | cards = cardsinstance.copy() 27 | 28 | printG("Importing libraries...", "blue") 29 | from PIL import Image, ImageFont, ImageDraw 30 | from Card import Card 31 | from time import gmtime, strftime 32 | import os 33 | import math 34 | 35 | 36 | # PRINT COLORS: 37 | # Blue = doing something -- header 38 | # Green = done something! 39 | # Yellow = important part of thing being done 40 | # Red = error 41 | 42 | printG("Rendering image:", "blue") 43 | mainimage = Image.new(mode = "RGB", size = (resx, resy)) 44 | maindraw = ImageDraw.Draw(mainimage) 45 | 46 | 47 | # Assets 48 | printG("Gathering fonts...", "blue") 49 | pixelratio = resx/(scale * 100) 50 | textsizes = [50*pixelratio, 40*pixelratio, 30*pixelratio, 15*pixelratio] 51 | fonts = [] 52 | for textsize in textsizes: 53 | fonts.append(ImageFont.truetype("Fonts/font1.ttf",round(textsize))) 54 | 55 | printG("Gathering colors...", "blue") 56 | colorfile = open('Colors.txt', 'r') 57 | colorfileLines = colorfile.readlines() 58 | colors = [] 59 | for line in colorfileLines: 60 | if "#" in line: 61 | break 62 | else: 63 | colors.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 64 | printG("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 65 | 66 | printG("All assets prepared.", "green") 67 | 68 | 69 | # Draw time 70 | # Reserve area under time for clock 71 | printG("Generating time...", "blue") 72 | 73 | maxclockheight = round((55*pixelratio)+(50*pixelratio)) 74 | maindraw.rectangle([(0,0),(resx, maxclockheight)], fill=colors[3]) 75 | 76 | maindraw.text((10*pixelratio,0), strftime("%I:%M"), fill=colors[1], font=fonts[0]) #Hour 77 | maindraw.text((150*pixelratio,10*pixelratio), strftime("%p"), fill=colors[1], font=fonts[1]) #AM/PM 78 | maindraw.text((10*pixelratio, 55*pixelratio), strftime("%A, %b %d, %Y"), fill=colors[0], font=fonts[2]) #Date 79 | 80 | printG("Generated: Time", "green") 81 | 82 | 83 | # Draw cards 84 | # Determine total number of cards 85 | printG("Calculating spaces...", "blue") 86 | cardsx = math.floor(resx/pixelratio/100) 87 | cardsy = math.floor((resy-maxclockheight)/pixelratio/100) 88 | printG("This SmartFrame (" + str(resx) + "x" + str(resy)+ ") can fit a " + str(cardsx) + "x" + str(cardsy) + " grid of cards.", "yellow") 89 | cardarray = [[False for x in range(cardsx)] for y in range(cardsy)] # False = unused, True = used 90 | 91 | # Display 1x1 card in time banner 92 | try: 93 | if len(cards) != 0: 94 | for card in cards: 95 | if card.tilesx == 1 and card.tilesy == 1: 96 | printG("Displaying card " + card.sourcename + " in banner...", "cyan") 97 | sizexy = maxclockheight 98 | image = card.Image(sizexy, sizexy) 99 | mainimage.paste(image, (round(mainimage.size[0]-(sizexy)),0)) 100 | printG("Card displayed successfully! Removing card from array...", "green") 101 | cards.pop(cards.index(card)) 102 | break 103 | except: 104 | import traceback 105 | logError("Error displaying a banner card! Did you move files around?", traceback.format_exc(), moduleName) 106 | 107 | nocards = False 108 | # Loop through each x and y. 109 | for y in range(0, cardsy): 110 | for x in range(0, cardsx): 111 | # If there are no more cards, stop finding places for them 112 | if len(cards) == 0: 113 | printG("Out of cards! Generation complete!", "green") 114 | nocards = True 115 | break 116 | 117 | # If space is filled or out of range, move to next space. 118 | try: 119 | if cardarray[y][x]: 120 | printG("("+str(x)+", "+str(y)+") is filled!") 121 | continue 122 | except IndexError: 123 | printG("("+str(x)+", "+str(y)+") is out of range!") 124 | continue 125 | # If space is not filled: 126 | else: 127 | #printG("("+str(x)+", "+str(y)+") is empty!") 128 | currentcard = "" 129 | enoughspace = True 130 | # Loop through all cards to find one that fits 131 | for i, currentcard in enumerate(cards): 132 | enoughspace = True 133 | rangeXstart = x 134 | rangeYstart = y 135 | rangeXend = x+currentcard.tilesx-1 136 | rangeYend = y+currentcard.tilesy-1 137 | 138 | currentcardrange = "[ (" + str(rangeXstart) + ", " + str(rangeYstart)+"), (" + str(rangeXend) + ", " + str(rangeYend) + ") ]" 139 | printG("Trying card ["+str(i)+"] " + currentcard.sourcename + " with size ("+ str(currentcard.tilesx) + ", " + str(currentcard.tilesy)+ ") in area " + currentcardrange + "...", "blue") 140 | used = False 141 | for cardy in range(rangeYstart,rangeYend+1): # +1 because the end case needs to be tested too 142 | for cardx in range(rangeXstart,rangeXend+1): 143 | #printG("Testing ("+str(cardx) + ", " + str(cardy) + ") !","cyan") 144 | try: 145 | if cardarray[cardy][cardx] == True: 146 | # If card doesn't fit, try next card 147 | used = True 148 | except IndexError: 149 | # If card is too big, try another card 150 | printG("("+str(cardx)+", "+str(cardy)+") is out of range! Stopping check...") 151 | used = True 152 | if used: 153 | printG("("+str(cardx)+", "+str(cardy)+") is filled! Stopping check...") 154 | break 155 | if used: 156 | break 157 | if not used: 158 | break 159 | else: 160 | enoughspace = False 161 | if not enoughspace: 162 | # If there's not enough space for any card, iterate to next space: 163 | printG("Not enough space for any card in (" + str(x) + ", " + str(y) + ")!", "red") 164 | continue 165 | else: 166 | printG("All space for card " + currentcard.sourcename + " is empty! Stopping check...", "green") 167 | 168 | # set target cells to filled 169 | printG("Setting range " + currentcardrange + " to filled...") 170 | for cardy in range(y, y+currentcard.tilesy): 171 | for cardx in range(x, x+currentcard.tilesx): 172 | cardarray[cardy][cardx] = True 173 | for line in cardarray: 174 | print(str(line)) 175 | 176 | # place card! 177 | printG("Placing card " + currentcard.sourcename + " at " + currentcardrange + "...", "blue") 178 | xstart = x*(100*pixelratio) 179 | ystart = y*(100*pixelratio)+maxclockheight 180 | xend = (pixelratio*(100)*currentcard.tilesx)+xstart 181 | yend = (pixelratio*(100)*currentcard.tilesy)+ystart 182 | #maindraw.rectangle([(xstart,ystart),(xend, yend)], fill=colors[4], outline=colors[0], width = round(2*pixelratio)) # Debug 183 | try: 184 | image = currentcard.Image(xend-xstart, yend-ystart) 185 | mainimage.paste(image, (round(xstart),round(ystart))) 186 | except: 187 | import traceback 188 | logError("Error displaying a card! Did you move files around?", traceback.format_exc(), moduleName) 189 | 190 | # pop card from array! 191 | printG("Card placed successfully! Removing from array...", "green") 192 | cards.pop(cards.index(currentcard)) 193 | printG("There are " + str(len(cards)) + " cards remaining...", "yellow") 194 | if nocards: 195 | break 196 | 197 | 198 | # Alt text / overflow 199 | alttext = "" 200 | if len(cards) != 0: 201 | alttext = cards[0].sourcename + ": " + cards[0].alttext 202 | if len(cards) > 1: 203 | remainingcards = len(cards) 204 | if remainingcards > 1: 205 | alttext+=" and " + str(remainingcards) + " more cards" 206 | else: 207 | alttext+=" and 1 more card" 208 | printG("Some cards remaining! Setting alttext to \"" + alttext + "\"") 209 | if alttext: 210 | maindraw.rectangle([(0,resy-(30*pixelratio)-5),(resx, resy)], fill=colors[3]) 211 | maindraw.text((10*pixelratio, resy-(30*pixelratio)), alttext, fill=colors[2], font=fonts[3]) 212 | 213 | # Done! 214 | printG("Image generated!", "green") 215 | return mainimage 216 | except KeyboardInterrupt: 217 | print("Keyboard interrupt detected! Exiting...") 218 | exit(0) 219 | except: 220 | import traceback 221 | logError("There was an error!", traceback.format_exc(), moduleName) 222 | return mainimage 223 | 224 | 225 | 226 | # ~~~Test code~~~ 227 | #from PIL import Image, ImageFont, ImageDraw 228 | #from Card import Card 229 | #from time import gmtime, strftime 230 | #import os 231 | #import math 232 | #files = os.listdir("Cards") 233 | 234 | #cards = [Card(files[0], "poggers", files[0], 2, 2), 235 | #Card(files[1], "poggers", files[1], 4, 4), 236 | #Card(files[2], "poggers", files[2], 4, 2), 237 | #Card(files[3], "poggers", files[3], 1, 1)] 238 | 239 | #GenerateImage(cards, 720, 1280, 4).show() 240 | #GenerateImage(cards, 1080, 1920, 8).show() 241 | #GenerateImage(cards, 1920, 1080, 24).show() -------------------------------------------------------------------------------- /Plugin Templates/1UnitImage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- 1UnitImage.py 3 | # This is a plugin template for SmartFrame v2. 4 | # This particular template lets you display an icon and a custom background. 5 | # MAKE SURE THAT YOUR ICON IS OBVIOUS -- for example a lightbulb icon could refer to Philips Hue or a hint/tip icon. 6 | 7 | # Required deps: Pillow, termcolor 8 | 9 | # DEVELOPER INSTRUCTIONS: 10 | # Assume you have root priveleges. 11 | # Use the variables in GetCardData() to change the tile size and pixel density of your Card. 12 | # If you need additional files, place them into a folder of the same name as your plugin. 13 | # Use the global variable SMARTFRAMEFOLDER for a string with the location of the SmartFrame folder. 14 | # For debug/overflow purposes, make sure you set alttext to something that accurately represents your collected data. 15 | # Use printC(text, color of text) if you need to print. 16 | # If you need to throw an error, use logError("main error description", "more detailed traceback / traceback.format_exc()", sourcename) 17 | # The above will log to both console and the standard error logging file. 18 | 19 | # Make sure to change the sourcename, imagePath, background, and alttext variables! 20 | 21 | # To test, just run your card in a terminal! The image will appear in your Smartframe/Cards folder. I recommend deleting this file before running SmartFrame again. 22 | # Note that if your plugin crashes, it will not take down the whole SmartFrame process. However, tracebacks will be outputted to the user. 23 | 24 | # When you're ready to release to the main repo, place all your code and related files in a folder and place it into Available Plugins/, then make a pull request! 25 | 26 | # Add any user-definable variables here! (API keys, usernames, etc.) 27 | sourcename = "Set your card's default sourcename here" 28 | 29 | from PIL import Image, ImageFont, ImageDraw 30 | import os 31 | import sys 32 | 33 | SMARTFRAMEFOLDER = "" 34 | COLORS = [] 35 | 36 | #### YOUR CODE HERE #### 37 | def GetCardData(): 38 | imagePath = "" # File within same folder as plugin (I recommend creating a subfolder with assets related to your plugin 39 | 40 | background = (100,0,0) # Red, Green, Blue 41 | alttext = "Whatever you want!" 42 | 43 | # Your code here 44 | 45 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 46 | index = file.rfind("/") 47 | file = file[:index] 48 | fullImagePath = file + "/" + imagePath # File location of image 49 | 50 | fullImagePath = "" 51 | 52 | return fullImagePath, background, alttext 53 | #### YOUR CODE HERE #### 54 | 55 | def GenerateCard(): 56 | tilesX = 1 # Change this to change tile size 57 | tilesY = 1 # Change this to change tile size 58 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 59 | imageresx = tilesX*dpifactor 60 | imageresy = tilesY*dpifactor 61 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 62 | imagedraw = ImageDraw.Draw(image) 63 | 64 | imageFile, background, alttext = GetCardData() 65 | 66 | if imageFile and background and alttext: 67 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=background) 68 | try: 69 | icon = Image.open(imageFile) 70 | except: 71 | import traceback 72 | logError("Unable to open image! Check the traceback.", traceback.format_exc(), sourcename) 73 | return None, None, None, None 74 | icon = icon.resize((round(imageresx-(dpifactor/12)), round(imageresy-(dpifactor/12)))) 75 | try: 76 | image.paste(icon, (round(dpifactor/25), round(dpifactor/25)), mask=icon) 77 | except: 78 | printC("Error with transparency! Trying without...", "red") 79 | try: 80 | image.paste(icon, (round(dpifactor/25), round(dpifactor/25))) 81 | except: 82 | import traceback 83 | logError("Unable to display image! Check the traceback.", traceback.format_exc(), sourcename) 84 | return None, None, None, None 85 | else: 86 | printC("No data! Sending null data.", "red") 87 | return None, None, None, None 88 | 89 | return image, alttext, tilesX, tilesY 90 | 91 | 92 | def printC(string, color = "white"): 93 | from termcolor import colored 94 | print(sourcename + " | " + colored(str(string), color)) 95 | 96 | def GetPresets(): 97 | # Set presets 98 | printC("Getting deps...", "blue") 99 | currentLocation = os.getcwd().replace('\\', '/') 100 | nextindex = currentLocation.rfind("/") 101 | global SMARTFRAMEFOLDER 102 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 103 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 104 | else: 105 | SMARTFRAMEFOLDER = currentLocation 106 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 107 | 108 | sys.path.append(SMARTFRAMEFOLDER) 109 | 110 | printC("Gathering colors...", "blue") 111 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 112 | colorfileLines = colorfile.readlines() 113 | global COLORS 114 | for line in colorfileLines: 115 | if "#" in line: 116 | break 117 | else: 118 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 119 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 120 | 121 | GetPresets() 122 | from ErrorLogger import logError 123 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 124 | def GetCard(): 125 | 126 | # Generate card... 127 | printC("Starting card generation...", "blue") 128 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 129 | 130 | # Check if card exists 131 | if image and alttext and tilesX and tilesY: 132 | printC("Finished generating card!...", "green") 133 | 134 | 135 | # Setup output location 136 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 137 | printC("Will output to " + outputLocation, "cyan") 138 | 139 | # Save 140 | image.save(outputLocation) 141 | printC("Image saved to " + outputLocation + "!", "green") 142 | 143 | from Card import Card 144 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 145 | else: 146 | # No cards 147 | printC("No cards to return!...", "red") 148 | return None 149 | 150 | if __name__ == "__main__": 151 | GetCard() 152 | -------------------------------------------------------------------------------- /Plugin Templates/FullCustomTemplate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- FullCustomTemplate.py 3 | # This is a plugin template for SmartFrame v2. 4 | # This particular template is fully custom -- you can set your output to whatever image or tile size you'd like. 5 | 6 | # Required deps: Pillow, termcolor 7 | 8 | # DEVELOPER INSTRUCTIONS: 9 | # Assume you have root priveleges. 10 | # Use the variables in GenerateCard() to change the tile size and pixel density of your Card. 11 | # If you need additional files, place them into a folder of the same name as your plugin. 12 | # The module will grab the user's colors before running your code. This will be located in COLORS 13 | # The user's font is located in SMARTFRAMEFOLDER + "/Fonts/font1.ttf". 14 | # Use the global variable SMARTFRAMEFOLDER for a string with the location of the SmartFrame folder. 15 | # For debug/overflow purposes, make sure you set alttext to something that accurately represents your collected data. 16 | # Use printC(text, color of text) if you need to print. 17 | # If you need to throw an error, use logError("main error description", "more detailed traceback / traceback.format_exc()", sourcename) 18 | # The above will log to both console and the standard error logging file. 19 | 20 | # Edit the PIL 'image' variable in GenerateCard in any way that you like! The end result of the variable will be what appears in SmartFrame. 21 | # If you return set all variables to None (ex, if data can't be found), SmartFrame will display nothing for this Card. 22 | 23 | # To test, just run your card in a terminal! The image will appear in your Smartframe/Cards folder. I recommend deleting this file before running SmartFrame again. 24 | # Note that if your plugin crashes, it will not take down the whole SmartFrame process. However, tracebacks will be outputted to the user. 25 | 26 | # When you're ready to release to the main repo, place all your code and related files in a folder and place it into Available Plugins/, then make a pull request! 27 | 28 | # Add any user-definable variables here! (API keys, usernames, etc.) 29 | sourcename = "Set your card's default sourcename here" 30 | 31 | from PIL import Image, ImageFont, ImageDraw 32 | import os 33 | import sys 34 | 35 | SMARTFRAMEFOLDER = "" 36 | COLORS = [] 37 | 38 | #### YOUR CODE HERE #### 39 | def GenerateCard(): 40 | tilesX = 4 # Change this to change tile size 41 | tilesY = 2 # Change this to change tile size 42 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 43 | imageresx = tilesX*dpifactor 44 | imageresy = tilesY*dpifactor 45 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 46 | alttext = "" 47 | imagedraw = ImageDraw.Draw(image) 48 | 49 | # Your code here 50 | 51 | return image, alttext, tilesX, tilesY 52 | #### YOUR CODE HERE #### 53 | 54 | 55 | def printC(string, color = "white"): 56 | from termcolor import colored 57 | print(sourcename + " | " + colored(str(string), color)) 58 | 59 | def GetPresets(): 60 | # Set presets 61 | printC("Getting deps...", "blue") 62 | currentLocation = os.getcwd().replace('\\', '/') 63 | nextindex = currentLocation.rfind("/") 64 | global SMARTFRAMEFOLDER 65 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 66 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 67 | else: 68 | SMARTFRAMEFOLDER = currentLocation 69 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 70 | 71 | sys.path.append(SMARTFRAMEFOLDER) 72 | 73 | printC("Gathering colors...", "blue") 74 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 75 | colorfileLines = colorfile.readlines() 76 | global COLORS 77 | for line in colorfileLines: 78 | if "#" in line: 79 | break 80 | else: 81 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 82 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 83 | 84 | GetPresets() 85 | from ErrorLogger import logError 86 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 87 | def GetCard(): 88 | 89 | # Generate card... 90 | printC("Starting card generation...", "blue") 91 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 92 | 93 | # Check if card exists 94 | if image and alttext and tilesX and tilesY: 95 | printC("Finished generating card!...", "green") 96 | 97 | 98 | # Setup output location 99 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 100 | printC("Will output to " + outputLocation, "cyan") 101 | 102 | # Save 103 | image.save(outputLocation) 104 | printC("Image saved to " + outputLocation + "!", "green") 105 | 106 | from Card import Card 107 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 108 | else: 109 | # No cards 110 | printC("No cards to return!...", "red") 111 | return None 112 | 113 | if __name__ == "__main__": 114 | GetCard() 115 | -------------------------------------------------------------------------------- /Plugin Templates/ListOfObjects.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- ListOfObjects.py 3 | # This is a plugin template for SmartFrame v2. 4 | # This template works similarly to ObjectGroups, but instead displays Item objects in a grid. Similar to Apple HomeKit UI 5 | # Useful for things like weather, smart home devices like plugs, or iCloud device batteries. 6 | 7 | # Required deps: Pillow, termcolor 8 | 9 | # DEVELOPER INSTRUCTIONS: 10 | # Assume you have root priveleges. 11 | # Use the variables in GetCardData() to change the tile size and pixel density of your Card. 12 | # If you need additional files, place them into a folder of the same name as your plugin. 13 | # Use the global variable SMARTFRAMEFOLDER for a string with the location of the SmartFrame folder. 14 | # For debug/overflow purposes, make sure you set alttext to something that accurately represents your collected data. 15 | # Use printC(text, color of text) if you need to print. 16 | # If you need to throw an error, use logError("main error description", "more detailed traceback / traceback.format_exc()", sourcename) 17 | # The above will log to both console and the standard error logging file. 18 | 19 | # Similar to ObjectGroups, create and return a list of Items to be displayed in a grid. 20 | # Item names ARE DISPLAYED TO THE USER in this plugin. 21 | # Remember to set maintext, alttext, and sourcename! 22 | 23 | # To test, just run your card in a terminal! The image will appear in your Smartframe/Cards folder. I recommend deleting this file before running SmartFrame again. 24 | # Note that if your plugin crashes, it will not take down the whole SmartFrame process. However, tracebacks will be outputted to the user. 25 | 26 | # When you're ready to release to the main repo, place all your code and related files in a folder and place it into Available Plugins/, then make a pull request! 27 | 28 | # Add any user-definable variables here! (API keys, usernames, etc.) 29 | sourcename = "Set your card's default sourcename here" 30 | 31 | from PIL import Image, ImageFont, ImageDraw 32 | import os 33 | import sys 34 | 35 | SMARTFRAMEFOLDER = "" 36 | COLORS = [] 37 | 38 | ### YOUR CODE HERE ### 39 | def GetCardData(): 40 | def GetPathWithinNeighbouringFolder(fileWithin, folder): 41 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 42 | index = file.rfind("/") 43 | file = file[:index] 44 | fullImagePath = file + "/" + folder + "/" + fileWithin # File location of image 45 | return fullImagePath 46 | 47 | # Sample code 48 | #itemList = [Item("item", "/path/to/image", (255,255,255), bgFillAmt=0.5), Item("item", "/path/to/image/2", (255,255,255), bgFillAmt=0.5)] 49 | itemList = [] 50 | maintext = "Some Items" 51 | alttext = "Whatever you want!" 52 | 53 | # Your code here 54 | 55 | return itemList, maintext, alttext 56 | ### YOUR CODE HERE ### 57 | 58 | 59 | def GenerateCard(): 60 | itemList, maintext, alttext = GetCardData() 61 | if itemList: 62 | if len(itemList) > 8: 63 | tilesX = 4 64 | tilesY = 4 65 | elif len(itemList) > 4: 66 | tilesX = 4 67 | tilesY = 2 68 | else: 69 | tilesX = 2 70 | tilesY = 2 71 | 72 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 73 | imageresx = tilesX*dpifactor 74 | imageresy = tilesY*dpifactor 75 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 76 | imagedraw = ImageDraw.Draw(image) 77 | 78 | # Draw background 79 | backgroundcolor = COLORS[3] # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 80 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 81 | 82 | # Draw Item tiles 83 | padding = dpifactor/50 84 | xcount = 0 85 | ycount = 0 86 | for item in itemList: 87 | printC("Getting image for item " + item.itemName) 88 | try: 89 | itemImg = item.Image(round(dpifactor-(2*padding)-round(dpifactor/8)), round(dpifactor/6)) 90 | except: 91 | import traceback 92 | logError("Unknown error with image " + item.itemName + "! Moving on to next image...", traceback.format_exc(), sourcename) 93 | continue 94 | image.paste(itemImg, (round((dpifactor/(16/tilesX))+padding+(xcount*(dpifactor-(dpifactor/8)))), round(padding+(ycount*(dpifactor-(dpifactor/8))))), mask=itemImg) 95 | if xcount+1 == tilesX: 96 | xcount = 0 97 | ycount += 1 98 | else: 99 | xcount += 1 100 | 101 | # Draw maintext 102 | maintextcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 103 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 12*round(dpifactor/50)) 104 | imagedraw.text((round(padding), round(imageresy-(dpifactor/3))), maintext, fill=maintextcolor, font=maintextfont) 105 | else: 106 | printC("No data! Sending null data.", "red") 107 | return None, None, None, None 108 | 109 | return image, alttext, tilesX, tilesY 110 | 111 | 112 | # Item class in ObjectGroup 113 | class Item: 114 | itemName = "" 115 | iconLocation = "" # Icon file path 116 | bgColor = (0,0,0) # Background color of item 117 | bgFillAmt = 1 # Percentage (0 - 1) of background filledness, useful to show smart light brightness 118 | 119 | def __init__(self, itemName, iconLocation, bgColor, bgFillAmt=1): 120 | self.itemName = itemName 121 | self.iconLocation = iconLocation 122 | self.bgColor = bgColor 123 | self.bgFillAmt = bgFillAmt 124 | print("New Item | " + self.itemName + " with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor)) 125 | 126 | # Returns a pretty Item with a rounded-rectangle background 127 | def Image(self, xyres, cornerrad): 128 | 129 | # Create the image of provided size 130 | image = Image.new(mode="RGBA", size = (xyres, xyres)) 131 | imagedraw = ImageDraw.Draw(image) 132 | 133 | # This background changes height based on the fill amount. Useful for smart light brightness or speaker volume. 134 | imagedraw.rounded_rectangle([(0,0),(xyres,xyres)], fill=(255,255,255,100), radius=cornerrad) # BG 135 | imagedraw.rounded_rectangle([(0,round(xyres*(1-self.bgFillAmt))),(xyres,xyres)], fill=(round(self.bgColor[0]), round(self.bgColor[1]), round(self.bgColor[2])), radius=round(cornerrad)) # FG 136 | 137 | # Overlay the icon 138 | icon = Image.open(self.iconLocation) 139 | icon = icon.resize((round(xyres-(xyres/3)), round(xyres-(xyres/3)))) 140 | image.paste(icon, (round(xyres/6), round(xyres/10)), mask=icon) 141 | 142 | # Add itemname text 143 | itemtextcolor = (0,0,0) # To change the appearance of maintext font, backgrounds, or anything else, head to GenerateCard()! 144 | itemtextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", round(xyres/8)) 145 | imagedraw.text((cornerrad, 4*xyres/5), self.itemName, fill=itemtextcolor, font=itemtextfont) 146 | 147 | return image 148 | 149 | def __str__(self): 150 | return "Item object: " + self.itemName + "' with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor) 151 | 152 | def printC(string, color = "white"): 153 | from termcolor import colored 154 | print(sourcename + " | " + colored(str(string), color)) 155 | 156 | def GetPresets(): 157 | # Set presets 158 | printC("Getting deps...", "blue") 159 | currentLocation = os.getcwd().replace('\\', '/') 160 | nextindex = currentLocation.rfind("/") 161 | global SMARTFRAMEFOLDER 162 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 163 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 164 | else: 165 | SMARTFRAMEFOLDER = currentLocation 166 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 167 | 168 | sys.path.append(SMARTFRAMEFOLDER) 169 | 170 | printC("Gathering colors...", "blue") 171 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 172 | colorfileLines = colorfile.readlines() 173 | global COLORS 174 | for line in colorfileLines: 175 | if "#" in line: 176 | break 177 | else: 178 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 179 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 180 | 181 | GetPresets() 182 | from ErrorLogger import logError 183 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 184 | def GetCard(): 185 | 186 | # Generate card... 187 | printC("Starting card generation...", "blue") 188 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 189 | # Check if card exists 190 | if image and alttext and tilesX and tilesY: 191 | printC("Finished generating card!...", "green") 192 | 193 | 194 | # Setup output location 195 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 196 | printC("Will output to " + outputLocation, "cyan") 197 | 198 | # Save 199 | image.save(outputLocation) 200 | printC("Image saved to " + outputLocation + "!", "green") 201 | 202 | from Card import Card 203 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 204 | else: 205 | # No cards 206 | printC("No cards to return!...", "red") 207 | return None 208 | 209 | if __name__ == "__main__": 210 | GetCard() 211 | -------------------------------------------------------------------------------- /Plugin Templates/MediaPlayer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- MediaPlayer.py 3 | # This is a plugin template for SmartFrame v2. 4 | # This particular template is meant for media 5 | #it can display an album art and generates a nicely formatted Card with progress, song and artist names, 6 | #and other information. 7 | 8 | # Required deps: Pillow, termcolor, colorgram.py 9 | 10 | # DEVELOPER INSTRUCTIONS: 11 | # Assume you have root priveleges. 12 | # Use the variables in GetCardData() to change the tile size and pixel density of your Card. 13 | # If you need additional files, place them into a folder of the same name as your plugin. 14 | # Use GetPathWithinNeighbouringFolder to get the files from this folder. 15 | # Make sure that the image isn't too large! A large image can take a long time for weaker computers to process colors from. 16 | 17 | 18 | # Make sure to change the following variables: 19 | # sourcename, progress, songname, artistname, albumArtLocation, othertext, alttext. 20 | 21 | # For debug/overflow purposes, make sure you set alttext to something that accurately represents your collected data. 22 | # Use printC(text, color of text) if you need to print. 23 | # If you need to throw an error, use logError("main error description", "more detailed traceback / traceback.format_exc()", sourcename) 24 | # The above will log to both console and the standard error logging file. 25 | 26 | # To test, just run your card in a terminal! The image will appear in your Smartframe/Cards folder. I recommend deleting this file before running SmartFrame again. 27 | # Note that if your plugin crashes, it will not take down the whole SmartFrame process. However, tracebacks will be outputted to the user. 28 | 29 | # When you're ready to release to the main repo, place all your code and related files in a folder and place it into Available Plugins/, then make a pull request! 30 | 31 | # Add any user-definable variables here! (API keys, usernames, etc.) 32 | sourcename = "Set your card's default sourcename here" 33 | 34 | from PIL import Image, ImageFont, ImageDraw 35 | import os 36 | import sys 37 | import colorgram 38 | 39 | SMARTFRAMEFOLDER = "" 40 | COLORS = [] 41 | 42 | ### YOUR CODE HERE ### 43 | def GetCardData(): 44 | 45 | def GetPathWithinNeighbouringFolder(fileWithin, folder): 46 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 47 | index = file.rfind("/") 48 | file = file[:index] 49 | fullImagePath = file + "/" + folder + "/" + fileWithin # File location of image 50 | return fullImagePath 51 | 52 | progress = 0.5 # Float between 0 and 1 53 | songname = "Megalovania" 54 | artistname = "Toby Fox" 55 | albumArtLocation = GetPathWithinNeighbouringFolder("", "") 56 | othertext = "Your App Name\nStatus" 57 | alttext = "Whatever you want!" 58 | 59 | # Your code here 60 | 61 | return progress, songname, artistname, albumArtLocation, othertext, alttext 62 | ### YOUR CODE HERE ### 63 | 64 | 65 | 66 | def GenerateCard(): 67 | tilesX = 2 # Change this to change tile size 68 | tilesY = 2 # Change this to change tile size 69 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 70 | backgroundOverrideColor = COLORS[3] # This color will be displayed in the background if album art can't be found or displayed. 71 | songtextcolor = COLORS[0] 72 | artisttextcolor = COLORS[1] 73 | songtextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 12*round(dpifactor/50)) 74 | artisttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 10*round(dpifactor/50)) 75 | othertextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 7*round(dpifactor/50)) 76 | 77 | imageresx = tilesX*dpifactor 78 | imageresy = tilesY*dpifactor 79 | image = Image.new("RGBA", (tilesX*dpifactor, tilesY*dpifactor)) 80 | 81 | padding = round(dpifactor/25) 82 | 83 | progress, songname, artistname, albumArtLocation, othertext, alttext = GetCardData() 84 | 85 | if songname: 86 | 87 | deleteimage = True 88 | 89 | try: 90 | printC("Running colorgram...", "blue") 91 | albumartcolour = colorgram.extract(albumArtLocation, 1)[0].rgb 92 | printC("Colorgram done!", "green") 93 | 94 | # Tune colours 95 | import colorsys 96 | albumartcolourhsv = colorsys.rgb_to_hsv(albumartcolour[0]/255, albumartcolour[1]/255, albumartcolour[2]/255) 97 | if albumartcolourhsv[2] > 0.9: 98 | printC("Superbright!!") 99 | albumartcolour = (albumartcolour[0]-200, albumartcolour[1]-200, albumartcolour[2]-200) 100 | progressbarcolor = (albumartcolour[0]+150, albumartcolour[1]+150, albumartcolour[2]+150) 101 | transparency = 220 102 | elif albumartcolourhsv[2] > 0.6: 103 | printC("Bright!!") 104 | albumartcolour = (albumartcolour[0]-100, albumartcolour[1]-100, albumartcolour[2]-100) 105 | progressbarcolor = (albumartcolour[0]+150, albumartcolour[1]+150, albumartcolour[2]+150) 106 | transparency = 180 107 | elif albumartcolourhsv[2] < 0.2: 108 | printC("Dark!!") 109 | albumartcolour = (albumartcolour[0], albumartcolour[1], albumartcolour[2]) 110 | progressbarcolor = (albumartcolour[0]+175, albumartcolour[1]+175, albumartcolour[2]+175) 111 | transparency = 150 112 | else: 113 | printC("Normal!!") 114 | progressbarcolor = (albumartcolour[0]+100, albumartcolour[1]+100, albumartcolour[2]+100) 115 | transparency = 100 116 | 117 | # Get album art 118 | albumart = Image.open(albumArtLocation) 119 | albumart = albumart.resize((imageresx, imageresy)) 120 | image.paste(albumart, (0, 0)) 121 | 122 | # Background darken overlay thing 123 | overlay = Image.new("RGBA", (imageresx, imageresy)) 124 | overlayDraw = ImageDraw.Draw(overlay) 125 | overlayDraw.rectangle([(0,0), (imageresx, imageresy)], fill=(albumartcolour[0], albumartcolour[1], albumartcolour[2], transparency)) # Semitransparent overlay for text contrast 126 | image = Image.alpha_composite(image, overlay) 127 | 128 | except: 129 | imagedraw = ImageDraw.Draw(image) 130 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundOverrideColor) # Background if it can't get an image 131 | progressbarcolor = (backgroundOverrideColor[0]+50, backgroundOverrideColor[1]+50, backgroundOverrideColor[2]+50) 132 | import traceback 133 | logError("Unable to display album art or set progress bar color! Check out the traceback!", traceback.format_exc(), sourcename) 134 | deleteimage = False 135 | 136 | imagedraw = ImageDraw.Draw(image) 137 | 138 | imagedraw.text((padding, padding*2), songname, font=songtextfont, fill=songtextcolor) # Song name 139 | imagedraw.text((padding, (padding*2)+round(dpifactor/4)), artistname, font=artisttextfont, fill=artisttextcolor) # Artist name 140 | imagedraw.text((padding, imageresy-round(5*dpifactor/12)), othertext, font=othertextfont, fill=artisttextcolor) # App name 141 | overlay = Image.new("RGBA", (imageresx, imageresy)) 142 | overlayDraw = ImageDraw.Draw(overlay) 143 | overlayDraw.rounded_rectangle([(padding, round(2*imageresy/3)), (imageresx-padding, round(2*imageresy/3)+round(4*padding))], fill=(255,255,255,50), radius=round(dpifactor/5)) # Progress meter BG 144 | image = Image.alpha_composite(image, overlay) 145 | imagedraw = ImageDraw.Draw(image) 146 | if progress >= 0.1: 147 | imagedraw.rounded_rectangle([(padding, round(2*imageresy/3)), (progress*(imageresx-padding), round(2*imageresy/3)+round(4*padding))], fill=progressbarcolor, radius=round(dpifactor/5)) # Progress meter FG 148 | 149 | else: 150 | printC("No data! Sending null data.", "red") 151 | return None, None, None, None 152 | 153 | return image, alttext, tilesX, tilesY 154 | 155 | def printC(string, color = "white"): 156 | from termcolor import colored 157 | print(sourcename + " | " + colored(str(string), color)) 158 | 159 | def GetPresets(): 160 | # Set presets 161 | printC("Getting deps...", "blue") 162 | currentLocation = os.getcwd().replace('\\', '/') 163 | nextindex = currentLocation.rfind("/") 164 | global SMARTFRAMEFOLDER 165 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 166 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 167 | else: 168 | SMARTFRAMEFOLDER = currentLocation 169 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 170 | 171 | sys.path.append(SMARTFRAMEFOLDER) 172 | 173 | printC("Gathering colors...", "blue") 174 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 175 | colorfileLines = colorfile.readlines() 176 | global COLORS 177 | for line in colorfileLines: 178 | if "#" in line: 179 | break 180 | else: 181 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 182 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 183 | 184 | GetPresets() 185 | from ErrorLogger import logError 186 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 187 | def GetCard(): 188 | 189 | # Generate card... 190 | printC("Starting card generation...", "blue") 191 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 192 | 193 | # Check if card exists 194 | if image and alttext and tilesX and tilesY: 195 | printC("Finished generating card!...", "green") 196 | 197 | 198 | # Setup output location 199 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 200 | printC("Will output to " + outputLocation, "cyan") 201 | 202 | # Save 203 | image.save(outputLocation) 204 | printC("Image saved to " + outputLocation + "!", "green") 205 | 206 | from Card import Card 207 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 208 | else: 209 | # No cards 210 | printC("No cards to return!...", "red") 211 | return None 212 | 213 | if __name__ == "__main__": 214 | GetCard() 215 | -------------------------------------------------------------------------------- /Plugin Templates/ObjectGroup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- ObjectGroup.py 3 | # This is a plugin template for SmartFrame v2. 4 | # This template lets you create and display groups of items, useful for smart lights or sonos/Chromecast plugins. 5 | 6 | # Required deps: Pillow, termcolor 7 | 8 | # DEVELOPER INSTRUCTIONS: 9 | # Assume you have root priveleges. 10 | # Use the variables in GetCardData() to change the tile size and pixel density of your Card. 11 | # If you need additional files, place them into a folder of the same name as your plugin. 12 | # Use the global variable SMARTFRAMEFOLDER for a string with the location of the SmartFrame folder. 13 | # For debug/overflow purposes, make sure you set alttext to something that accurately represents your collected data. 14 | # Use printC(text, color of text) if you need to print. 15 | # If you need to throw an error, use logError("main error description", "more detailed traceback / traceback.format_exc()", sourcename) 16 | # The above will log to both console and the standard error logging file. 17 | 18 | # Use the Item and Group class to create a group of Items and a list of Groups. This template is complex but when used correctly it can be very powerful. 19 | # Group names support line breaks. 20 | 21 | # To test, just run your card in a terminal! The image will appear in your Smartframe/Cards folder. I recommend deleting this file before running SmartFrame again. 22 | # Note that if your plugin crashes, it will not take down the whole SmartFrame process. However, tracebacks will be outputted to the user. 23 | 24 | # When you're ready to release to the main repo, place all your code and related files in a folder and place it into Available Plugins/, then make a pull request! 25 | 26 | # Add any user-definable variables here! (API keys, usernames, etc.) 27 | sourcename = "Set your card's default sourcename here" 28 | 29 | from PIL import Image, ImageFont, ImageDraw 30 | import os 31 | import sys 32 | import math 33 | 34 | SMARTFRAMEFOLDER = "" 35 | COLORS = [] 36 | 37 | ### YOUR CODE HERE ### 38 | def GetCardData(): 39 | def GetPathWithinNeighbouringFolder(fileWithin, folder): 40 | file = __file__.replace('\\', '/') # Remove this if you use a specific file path for your image or some other method. 41 | index = file.rfind("/") 42 | file = file[:index] 43 | fullImagePath = file + "/" + folder + "/" + fileWithin # File location of image 44 | return fullImagePath 45 | 46 | groupList = [] 47 | # Example code: 48 | #groupList = [Group("O Canada!", [Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,0,0), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,0,0), bgFillAmt=0.5)]), 49 | # Group("Our home and\nnative land!", [Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,0,0), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,0,0), bgFillAmt=0.5)]), 50 | # Group("True patriot love\nin all of us command", [Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,0,0), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,0,0), bgFillAmt=1), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,0,0), bgFillAmt=0.5)]), 51 | # Group("With glowing hearts\nwe see thee rise", [Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,0,0), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,0,0), bgFillAmt=0.5)]), 52 | # Group("The True North\nstrong and free!", [Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,0,0), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,255,255), bgFillAmt=0.5), Item("item", "C:\\Users\\mycoo\\OneDrive\\Pictures\\deathstar.png", (255,0,0), bgFillAmt=0.5)])] 53 | maintext = "Some Groups" 54 | alttext = "Whatever you want!" 55 | 56 | # YOUR CODE HERE! Good luck, soldier. 57 | 58 | # Create an Item(name, iconFilePath, backgroundColorTuple, backgroundFillPercentage) for each item 59 | # Create a Group(name, arrayOfItems) for each group 60 | # Return a list of Groups to be generated 61 | # Use GetPathWithinNeighbouringFolder to get icons related to your plugin. 62 | 63 | return groupList, maintext, alttext 64 | ### YOUR CODE HERE ### 65 | 66 | 67 | 68 | def GenerateCard(): 69 | 70 | # Get data 71 | groupList, maintext, alttext = GetCardData() 72 | 73 | # If data is present 74 | if groupList: 75 | # Calculate card height 76 | tilesX = 4 77 | tilesY = math.floor(len(groupList)/2)+1 78 | printC("There are " + str(len(groupList)) + " groups in this Card. The card is " + str(tilesY) + " units high.", "yellow") 79 | 80 | # Stuff 81 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 82 | imageresx = tilesX*dpifactor 83 | imageresy = tilesY*dpifactor 84 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 85 | imagedraw = ImageDraw.Draw(image) 86 | backgroundcolor = COLORS[3] # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 87 | maintextcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 88 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 20*round(dpifactor/50)) 89 | 90 | 91 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 92 | padding = dpifactor/50 93 | top = padding 94 | for group in groupList: 95 | printC("Getting Image for group " + group.groupName) 96 | try: 97 | groupImg = group.Image((round(imageresx-(padding*2)),round(11*dpifactor/24)), dpifactor/10) 98 | except: 99 | import traceback 100 | logError("Unknown error with group " + group.groupName + "! Moving on to next group...", traceback.format_exc(), sourcename) 101 | continue 102 | image.paste(groupImg, (round(padding), round(top)), mask = groupImg) 103 | top += (11*dpifactor/24) + padding 104 | imagedraw.text((round(padding), round(top)), maintext, fill=maintextcolor, font=maintextfont) 105 | else: 106 | printC("No data! Sending null data.", "red") 107 | return None, None, None, None 108 | 109 | return image, alttext, tilesX, tilesY 110 | 111 | # Item class in ObjectGroup 112 | class Item: 113 | itemName = "" 114 | iconLocation = "" # Icon file path 115 | bgColor = (0,0,0) # Background color of item 116 | bgFillAmt = 1 # Percentage (0 - 1) of background filledness, useful to show smart light brightness 117 | 118 | def __init__(self, itemName, iconLocation, bgColor, bgFillAmt=1): 119 | self.itemName = itemName 120 | self.iconLocation = iconLocation 121 | self.bgColor = bgColor 122 | self.bgFillAmt = bgFillAmt 123 | print("New Item | " + self.itemName + " with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor)) 124 | 125 | # Returns a pretty Item with a rounded-rectangle background 126 | def Image(self, xyres, cornerrad): 127 | 128 | # Create the image of provided size 129 | image = Image.new(mode="RGBA", size = (xyres, xyres)) 130 | imagedraw = ImageDraw.Draw(image) 131 | 132 | # This background changes height based on the fill amount. Useful for smart light brightness or speaker volume. 133 | imagedraw.rounded_rectangle([(0,0),(xyres,xyres)], fill=(255,255,255,100), radius=cornerrad) # BG 134 | imagedraw.rounded_rectangle([(0,round(xyres*(1-self.bgFillAmt))),(xyres,xyres)], fill=(round(self.bgColor[0]), round(self.bgColor[1]), round(self.bgColor[2])), radius=round(cornerrad)) # FG 135 | 136 | # Overlay the icon 137 | icon = Image.open(self.iconLocation) 138 | icon = icon.resize((round(xyres-(xyres/12)), round(xyres-(xyres/12)))) 139 | image.paste(icon, (round(xyres/25), round(xyres/25)), mask=icon) 140 | 141 | return image 142 | 143 | def __str__(self): 144 | return "Item object: " + self.itemName + "' with icon at " + self.iconLocation + ". " + str(self.bgFillAmt*100) + "% filled background of color " + str(self.bgColor) 145 | 146 | # Group class in ObjectGroup 147 | class Group: 148 | groupName = "" 149 | itemArray = [] 150 | 151 | def __init__(self, groupName, itemArray): 152 | self.groupName = groupName 153 | self.itemArray = itemArray 154 | 155 | nameList = "" 156 | for item in self.itemArray: 157 | nameList += item.itemName 158 | nameList += ", " 159 | print("New ItemGroup | " + self.groupName + " with items " + nameList) 160 | 161 | def Image(self, xyres, cornerrad): 162 | # Create image of provided size 163 | image = Image.new(mode="RGBA", size = xyres) 164 | imagedraw = ImageDraw.Draw(image) 165 | 166 | # Create background 167 | imagedraw.rounded_rectangle([(0,0),xyres], fill=(0,0,0, 100), radius=cornerrad) 168 | 169 | # Overlay Items 170 | padding = round(((2*xyres[0]/3)/6)/20) 171 | imageWidth = round(((2*xyres[0]/3)/6)-(padding*2)) 172 | leftmost = padding 173 | for item in self.itemArray: 174 | try: 175 | itemImage = item.Image(imageWidth, cornerrad) 176 | except: 177 | import traceback 178 | logError("Unknown error with image " + image.imageName + "! Moving on to next image...", traceback.format_exc(), sourcename) 179 | continue 180 | image.paste(itemImage, (leftmost, padding), mask=itemImage) 181 | leftmost += imageWidth+(padding) 182 | fontscalefactor = 15 / (self.groupName.count("\n")+1) 183 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", round(fontscalefactor*padding)) 184 | imagedraw.text((leftmost+padding, 0), self.groupName, font=maintextfont, fill=COLORS[1]) 185 | 186 | return image 187 | 188 | def __str__(self): 189 | nameList = "" 190 | for item in self.itemArray: 191 | nameList += item.itemName 192 | nameList += ", " 193 | print("ItemGroup: " + self.groupName + " with items " + nameList) 194 | 195 | def printC(string, color = "white"): 196 | from termcolor import colored 197 | print(sourcename + " | " + colored(str(string), color)) 198 | 199 | def GetPresets(): 200 | # Set presets 201 | printC("Getting deps...", "blue") 202 | currentLocation = os.getcwd().replace('\\', '/') 203 | nextindex = currentLocation.rfind("/") 204 | global SMARTFRAMEFOLDER 205 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 206 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 207 | else: 208 | SMARTFRAMEFOLDER = currentLocation 209 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 210 | 211 | sys.path.append(SMARTFRAMEFOLDER) 212 | 213 | printC("Gathering colors...", "blue") 214 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 215 | colorfileLines = colorfile.readlines() 216 | global COLORS 217 | for line in colorfileLines: 218 | if "#" in line: 219 | break 220 | else: 221 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 222 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 223 | 224 | GetPresets() 225 | from ErrorLogger import logError 226 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 227 | def GetCard(): 228 | 229 | # Generate card... 230 | printC("Starting card generation...", "blue") 231 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 232 | 233 | # Check if card exists 234 | if image and alttext and tilesX and tilesY: 235 | printC("Finished generating card!...", "green") 236 | 237 | 238 | # Setup output location 239 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 240 | printC("Will output to " + outputLocation, "cyan") 241 | 242 | # Save 243 | image.save(outputLocation) 244 | printC("Image saved to " + outputLocation + "!", "green") 245 | 246 | from Card import Card 247 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 248 | else: 249 | # No cards 250 | printC("No cards to return!...", "red") 251 | return None 252 | 253 | if __name__ == "__main__": 254 | GetCard() 255 | -------------------------------------------------------------------------------- /Plugin Templates/SingleNumber.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- SingleNumber.py 3 | # This is a plugin template for SmartFrame v2. 4 | # This particular template will let you show a single large number and some small text with a fancy background. 5 | 6 | # Required deps: Pillow, termcolor 7 | 8 | # DEVELOPER INSTRUCTIONS: 9 | # Assume you have root priveleges. 10 | # Use the variables in GetCardData() to change the tile size and pixel density of your Card. 11 | # If you need additional files, place them into a folder of the same name as your plugin. 12 | # Use the global variable SMARTFRAMEFOLDER for a string with the location of the SmartFrame folder. 13 | # For debug/overflow purposes, make sure you set alttext to something that accurately represents your collected data. 14 | # Use printC(text, color of text) if you need to print. 15 | # If you need to throw an error, use logError("main error description", "more detailed traceback / traceback.format_exc()", sourcename) 16 | # The above will log to both console and the standard error logging file. 17 | 18 | # Make sure to change the sourcename, count, alttext, and maintext variables! 19 | # count should be an integer greater than or equal to 0. 20 | # If you return set all variables to None (ex, if data can't be found), SmartFrame will display nothing for this Card. 21 | 22 | # To test, just run your card in a terminal! The image will appear in your Smartframe/Cards folder. I recommend deleting this file before running SmartFrame again. 23 | # Note that if your plugin crashes, it will not take down the whole SmartFrame process. However, tracebacks will be outputted to the user. 24 | 25 | # When you're ready to release to the main repo, place all your code and related files in a folder and place it into Available Plugins/, then make a pull request! 26 | 27 | # Add any user-definable variables here! (API keys, usernames, etc.) 28 | sourcename = "Set your card's default sourcename here" 29 | 30 | from PIL import Image, ImageFont, ImageDraw 31 | import os 32 | import sys 33 | import math 34 | 35 | SMARTFRAMEFOLDER = "" 36 | COLORS = [] 37 | 38 | #### YOUR CODE HERE #### 39 | def GetCardData(): 40 | count = 21 41 | maintext = "Some data" 42 | alttext = "Whatever you want!" 43 | 44 | # Your code here 45 | 46 | return count, maintext, alttext 47 | #### YOUR CODE HERE #### 48 | 49 | def GenerateCard(): 50 | # EDIT THESE TO CUSTOMIZE YOUR PLUGIN'S APPEARANCE! 51 | tilesX = 2 # I don't recommend changing these values for this template 52 | tilesY = 2 # I don't recommend changing these values for this template 53 | dpifactor = 200 # Change this to increase card resolution. Don't go too high!!! 54 | backgroundcolor = COLORS[3] # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 55 | textcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 56 | circlesbgcolor = (COLORS[3][0]-10, COLORS[3][1]-10, COLORS[3][2]-10) # Change this to a 3-value tuple to change the background color of your progress meter! 57 | printC("Counter circles background color is " + str(circlesbgcolor)) 58 | 59 | imageresx = tilesX*dpifactor 60 | imageresy = tilesY*dpifactor 61 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 62 | imagedraw = ImageDraw.Draw(image) 63 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 64 | 65 | count, maintext, alttext = GetCardData() 66 | 67 | if maintext and alttext: 68 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 15*round(dpifactor/50)) 69 | if count < 10: # Don't worry i hate this too 70 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 71 | elif count < 20: 72 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 73 | elif count < 100: 74 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 50*round(dpifactor/50)) 75 | elif count < 1000: 76 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 40*round(dpifactor/50)) 77 | elif count < 10000: 78 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 35*round(dpifactor/50)) 79 | elif count < 100000: 80 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 30*round(dpifactor/50)) 81 | else: 82 | counttextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 20*round(dpifactor/50)) 83 | 84 | # Logarithm 85 | try: 86 | logAmt = math.log((count),10) 87 | except ValueError: 88 | logAmt = 1 89 | if logAmt == 0: 90 | logAmt = 1 91 | printC("LogAmt is " + str(logAmt)) 92 | 93 | # Top position 94 | topdivamt = (128/(5*logAmt)) 95 | if topdivamt < 3: 96 | topdivamt = 3 97 | counttexttop = imageresx/topdivamt 98 | 99 | # Start position 100 | startdivamt = (2.2*logAmt)+3 101 | if count < 10: 102 | startdivamt = 3 103 | counttextstart = imageresx/startdivamt 104 | 105 | printC("Counter scale factors are (" + str(startdivamt) + ", " + str(topdivamt) + ")") 106 | 107 | # Circles 108 | if not count == 0: 109 | cols = math.ceil(math.sqrt(count)) 110 | rows = round(math.sqrt(count)) 111 | printC("Generating a ("+str(cols)+"x"+str(rows)+") grid of " + str(count) + " circles...") 112 | 113 | padding = imageresx/(4*cols) 114 | size = (imageresx/cols) - padding 115 | for i in range(0,count): 116 | col = i % cols 117 | row = math.floor(i/cols) 118 | xpos = (padding/2)+(size+padding)*col 119 | ypos = (padding/2)+(size+padding)*row 120 | imagedraw.ellipse((xpos, ypos, xpos+size, ypos+size), fill=circlesbgcolor) 121 | 122 | imagedraw.text((dpifactor/50,5*imageresy/8), maintext, font=maintextfont, fill=textcolor) 123 | imagedraw.text((counttextstart, counttexttop), str(count), font=counttextfont, fill=textcolor) # Counter text 124 | else: 125 | printC("No data! Sending null data.", "red") 126 | return None, None, None, None 127 | 128 | return image, alttext, tilesX, tilesY 129 | 130 | def printC(string, color = "white"): 131 | from termcolor import colored 132 | print(sourcename + " | " + colored(str(string), color)) 133 | 134 | def GetPresets(): 135 | # Set presets 136 | printC("Getting deps...", "blue") 137 | currentLocation = os.getcwd().replace('\\', '/') 138 | nextindex = currentLocation.rfind("/") 139 | global SMARTFRAMEFOLDER 140 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 141 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 142 | else: 143 | SMARTFRAMEFOLDER = currentLocation 144 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 145 | 146 | sys.path.append(SMARTFRAMEFOLDER) 147 | 148 | printC("Gathering colors...", "blue") 149 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 150 | colorfileLines = colorfile.readlines() 151 | global COLORS 152 | for line in colorfileLines: 153 | if "#" in line: 154 | break 155 | else: 156 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 157 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 158 | 159 | GetPresets() 160 | from ErrorLogger import logError 161 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 162 | def GetCard(): 163 | 164 | # Generate card... 165 | printC("Starting card generation...", "blue") 166 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 167 | 168 | # Check if card exists 169 | if image and alttext and tilesX and tilesY: 170 | printC("Finished generating card!...", "green") 171 | 172 | 173 | # Setup output location 174 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 175 | printC("Will output to " + outputLocation, "cyan") 176 | 177 | # Save 178 | image.save(outputLocation) 179 | printC("Image saved to " + outputLocation + "!", "green") 180 | 181 | from Card import Card 182 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 183 | else: 184 | # No cards 185 | printC("No cards to return!...", "red") 186 | return None 187 | 188 | if __name__ == "__main__": 189 | GetCard() 190 | 191 | -------------------------------------------------------------------------------- /Plugin Templates/SingleRadialProgress.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- FullCustomTemplate.py 3 | # This is a plugin template for SmartFrame v2. 4 | # This particular template will let you set a percentage value to be rendered into a circle and some text. 5 | 6 | # Required deps: Pillow, termcolor 7 | 8 | # DEVELOPER INSTRUCTIONS: 9 | # Assume you have root priveleges. 10 | # Use the variables in GetCardData() to change the tile size and pixel density of your Card. 11 | # If you need additional files, place them into a folder of the same name as your plugin. 12 | # Use the global variable SMARTFRAMEFOLDER for a string with the location of the SmartFrame folder. 13 | # For debug/overflow purposes, make sure you set alttext to something that accurately represents your collected data. 14 | # Use printC(text, color of text) if you need to print. 15 | # If you need to throw an error, use logError("main error description", "more detailed traceback / traceback.format_exc()", sourcename) 16 | # The above will log to both console and the standard error logging file. 17 | 18 | # Make sure to change the sourcename, progress, alttext, and maintext variables! 19 | # progress should be a float out of 1. 20 | # If you return set all variables to None (ex, if data can't be found), SmartFrame will display nothing for this Card. 21 | 22 | # To test, just run your card in a terminal! The image will appear in your Smartframe/Cards folder. I recommend deleting this file before running SmartFrame again. 23 | # Note that if your plugin crashes, it will not take down the whole SmartFrame process. However, tracebacks will be outputted to the user. 24 | 25 | # When you're ready to release to the main repo, place all your code and related files in a folder and place it into Available Plugins/, then make a pull request! 26 | 27 | # Add any user-definable variables here! (API keys, usernames, etc.) 28 | sourcename = "Set your card's default sourcename here" 29 | 30 | from PIL import Image, ImageFont, ImageDraw 31 | import os 32 | import sys 33 | 34 | SMARTFRAMEFOLDER = "" 35 | COLORS = [] 36 | 37 | #### YOUR CODE HERE #### 38 | def GetCardData(): 39 | progress = 0.02 40 | maintext = "Some data" 41 | alttext = "Whatever you want!" 42 | 43 | # Your code here 44 | 45 | return progress, maintext, alttext 46 | #### YOUR CODE HERE #### 47 | 48 | def GenerateCard(): 49 | # EDIT THESE TO CUSTOMIZE YOUR PLUGIN'S APPEARANCE! 50 | tilesX = 2 # I don't recommend changing these values for this template 51 | tilesY = 2 # I don't recommend changing these values for this template 52 | dpifactor = 400 # Change this to increase card resolution. Don't go too high!!! 53 | backgroundcolor = COLORS[3] # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 54 | textcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 55 | progressfillcolor = (COLORS[3][0]+50, COLORS[3][1]+50, COLORS[3][2]+50) # Change this to a 3-value tuple to change the color of your progress meter! 56 | printC("Progress bar fill color is " + str(progressfillcolor)) 57 | progressbgcolor = (COLORS[3][0]-50, COLORS[3][1]-50, COLORS[3][2]-50) # Change this to a 3-value tuple to change the background color of your progress meter! 58 | printC("Progress bar background color is " + str(progressbgcolor)) 59 | 60 | imageresx = tilesX*dpifactor 61 | imageresy = tilesY*dpifactor 62 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 63 | imagedraw = ImageDraw.Draw(image) 64 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 65 | 66 | progress, maintext, alttext = GetCardData() 67 | 68 | if maintext and alttext: 69 | maintextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 15*round(dpifactor/50)) 70 | if progress < 0.1: 71 | progresstextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 30*round(dpifactor/50)) 72 | progresstexttop = (imageresy/4)-(dpifactor/10) 73 | else: 74 | progresstextfont = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 20*round(dpifactor/50)) 75 | progresstexttop = 5*imageresy/16 76 | imagedraw.text((dpifactor/50,3*imageresy/4), maintext, font=maintextfont, fill=textcolor) 77 | circlepos = [(imageresx/8,(imageresy/8)-(dpifactor/10)),(7*imageresx/8, (7*imageresy/8)-(dpifactor/10))] 78 | imagedraw.arc(circlepos, start=135, end=45, fill=progressbgcolor, width=round(dpifactor/4)) # Background 79 | imagedraw.arc(circlepos, start=135, end=(270*progress)+135, fill=progressfillcolor, width=round(dpifactor/4)) # Background 80 | imagedraw.text(((imageresx/4)+(dpifactor/10), progresstexttop), str(round(progress*100))+"%", font=progresstextfont, fill=textcolor) 81 | else: 82 | printC("No data! Sending null data.", "red") 83 | return None, None, None, None 84 | 85 | return image, alttext, tilesX, tilesY 86 | 87 | 88 | 89 | def printC(string, color = "white"): 90 | from termcolor import colored 91 | print(sourcename + " | " + colored(str(string), color)) 92 | 93 | def GetPresets(): 94 | # Set presets 95 | printC("Getting deps...", "blue") 96 | currentLocation = os.getcwd().replace('\\', '/') 97 | nextindex = currentLocation.rfind("/") 98 | global SMARTFRAMEFOLDER 99 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 100 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 101 | else: 102 | SMARTFRAMEFOLDER = currentLocation 103 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 104 | 105 | sys.path.append(SMARTFRAMEFOLDER) 106 | 107 | printC("Gathering colors...", "blue") 108 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 109 | colorfileLines = colorfile.readlines() 110 | global COLORS 111 | for line in colorfileLines: 112 | if "#" in line: 113 | break 114 | else: 115 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 116 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 117 | 118 | GetPresets() 119 | from ErrorLogger import logError 120 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 121 | def GetCard(): 122 | 123 | # Generate card... 124 | printC("Starting card generation...", "blue") 125 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 126 | 127 | # Check if card exists 128 | if image and alttext and tilesX and tilesY: 129 | printC("Finished generating card!...", "green") 130 | 131 | 132 | # Setup output location 133 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 134 | printC("Will output to " + outputLocation, "cyan") 135 | 136 | # Save 137 | image.save(outputLocation) 138 | printC("Image saved to " + outputLocation + "!", "green") 139 | 140 | from Card import Card 141 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 142 | else: 143 | # No cards 144 | printC("No cards to return!...", "red") 145 | return None 146 | 147 | if __name__ == "__main__": 148 | GetCard() 149 | -------------------------------------------------------------------------------- /Plugin Templates/SmallText.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # RaddedMC's SmartFrame v2 -- SmallText.py 3 | # This is a plugin template for SmartFrame v2. 4 | # This particular template lets you set 4 lines of text in a 2x2 card or 4x2 card. 5 | 6 | # Required deps: Pillow, termcolor 7 | 8 | # DEVELOPER INSTRUCTIONS: 9 | # Assume you have root priveleges. 10 | # Use the variables in GetCardData() to change the tile size and pixel density of your Card. 11 | # If you need additional files, place them into a folder of the same name as your plugin. 12 | # Use the global variable SMARTFRAMEFOLDER for a string with the location of the SmartFrame folder. 13 | # For debug/overflow purposes, make sure you set alttext to something that accurately represents your collected data. 14 | # Use printC(text, color of text) if you need to print. 15 | # If you need to throw an error, use logError("main error description", "more detailed traceback / traceback.format_exc()", sourcename) 16 | # The above will log to both console and the standard error logging file. 17 | 18 | # Make sure to change the sourcename, lines 1-4, and alttext variables! 19 | # If you return set all variables to None (ex, if data can't be found), SmartFrame will display nothing for this Card. 20 | 21 | # To test, just run your card in a terminal! The image will appear in your Smartframe/Cards folder. I recommend deleting this file before running SmartFrame again. 22 | # Note that if your plugin crashes, it will not take down the whole SmartFrame process. However, tracebacks will be outputted to the user. 23 | 24 | # When you're ready to release to the main repo, place all your code and related files in a folder and place it into Available Plugins/, then make a pull request! 25 | 26 | # Add any user-definable variables here! (API keys, usernames, etc.) 27 | sourcename = "Set your card's default sourcename here" 28 | 29 | from PIL import Image, ImageFont, ImageDraw 30 | import os 31 | import sys 32 | 33 | SMARTFRAMEFOLDER = "" 34 | COLORS = [] 35 | 36 | #### YOUR CODE HERE #### 37 | def GetCardData(): 38 | line1 = sourcename 39 | line2 = "Line 2" 40 | line3 = "Line 3" 41 | line4 = "Line 4" 42 | alttext = "Whatever you want!" 43 | 44 | # Your code here 45 | 46 | return line1, line2, line3, line4, alttext 47 | #### YOUR CODE HERE #### 48 | 49 | def GenerateCard(): 50 | 51 | # EDIT THESE TO CUSTOMIZE YOUR PLUGIN'S APPEARANCE! 52 | tilesX = 2 # Change this to change tile size 53 | tilesY = 2 # Change this to change tile size 54 | dpifactor = 200 55 | # Change this to increase card resolution. Don't go too high!!! 56 | backgroundcolor = COLORS[3] # Change this to a 3-value tuple (255, 200, 100) to change the background colour! 57 | textcolor = COLORS[1] # Change this to a 3-value tuple to change the text colour! 58 | 59 | imageresx = tilesX*dpifactor 60 | imageresy = tilesY*dpifactor 61 | image = Image.new("RGB", (tilesX*dpifactor, tilesY*dpifactor)) 62 | imagedraw = ImageDraw.Draw(image) 63 | imagedraw.rectangle([(0,0), (imageresx, imageresy)], fill=backgroundcolor) 64 | 65 | line1, line2, line3, line4, alttext = GetCardData() 66 | if line1 and line2 and line3 and line4 and alttext: 67 | font = ImageFont.truetype(SMARTFRAMEFOLDER + "/Fonts/font1.ttf", 15*round(dpifactor/50)) 68 | imagedraw.text((dpifactor/50,0), line1, font=font, fill=textcolor) 69 | printC("Line 1: " + line1) 70 | imagedraw.text((dpifactor/50,imageresy/4), line2, font=font, fill=textcolor) 71 | printC("Line 2: " + line2) 72 | imagedraw.text((dpifactor/50,imageresy/2), line3, font=font, fill=textcolor) 73 | printC("Line 3: " + line3) 74 | imagedraw.text((dpifactor/50,3*imageresy/4), line4, font=font, fill=textcolor) 75 | printC("Line 4: " + line4) 76 | 77 | return image, alttext, tilesX, tilesY 78 | else: 79 | printC("No data! Sending null data.", "red") 80 | return None, None, None, None 81 | 82 | 83 | def printC(string, color = "white"): 84 | from termcolor import colored 85 | print(sourcename + " | " + colored(str(string), color)) 86 | 87 | def GetPresets(): 88 | # Set presets 89 | printC("Getting deps...", "blue") 90 | currentLocation = os.getcwd().replace('\\', '/') 91 | nextindex = currentLocation.rfind("/") 92 | global SMARTFRAMEFOLDER 93 | if currentLocation.endswith("Plugins") or currentLocation.endswith("Plugin Templates"): 94 | SMARTFRAMEFOLDER = currentLocation[:nextindex] 95 | else: 96 | SMARTFRAMEFOLDER = currentLocation 97 | printC("SmartFrame is located in " + SMARTFRAMEFOLDER, "green") 98 | 99 | sys.path.append(SMARTFRAMEFOLDER) 100 | 101 | printC("Gathering colors...", "blue") 102 | colorfile = open(SMARTFRAMEFOLDER + '/Colors.txt', 'r') 103 | colorfileLines = colorfile.readlines() 104 | global COLORS 105 | for line in colorfileLines: 106 | if "#" in line: 107 | break 108 | else: 109 | COLORS.append((int(line[0:3]), int(line[4:7]), int(line[8:11]))) 110 | printC("Added color " + line[0:3] + " " + line[4:7] + " " + line[8:11] + "!") 111 | 112 | GetPresets() 113 | from ErrorLogger import logError 114 | ### SmartFrame.py calls this to get a card. I don't recommend editing this. 115 | def GetCard(): 116 | 117 | # Generate card... 118 | printC("Starting card generation...", "blue") 119 | image, alttext, tilesX, tilesY = GenerateCard() # Calls the above function to get data 120 | 121 | # Check if card exists 122 | if image and alttext and tilesX and tilesY: 123 | printC("Finished generating card!...", "green") 124 | 125 | 126 | # Setup output location 127 | outputLocation = SMARTFRAMEFOLDER + "/Cards/" + sourcename + ".png" 128 | printC("Will output to " + outputLocation, "cyan") 129 | 130 | # Save 131 | image.save(outputLocation) 132 | printC("Image saved to " + outputLocation + "!", "green") 133 | 134 | from Card import Card 135 | return Card(outputLocation, alttext, sourcename, tilesX, tilesY) 136 | else: 137 | # No cards 138 | printC("No cards to return!...", "red") 139 | return None 140 | 141 | if __name__ == "__main__": 142 | GetCard() 143 | -------------------------------------------------------------------------------- /Plugins/placed here to stop git from deleting it bad git: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddedMC/SmartFrame/fca787ba5df55861a6bb06b6c99107cc92b4b859/Plugins/placed here to stop git from deleting it bad git -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # RaddedMC's SmartFrame 2 | 3 | 4 | 5 | Welcome to the official SmartFrame project repository! This program is designed to grab your favourite data from all over the internet (weather, Smarthome/IoT, Spotify, Chromecast, etc) and display it in a neatly formatted image. | 6 | --- | --- 7 | 8 | You can use SmartFrame to send data to displays like a [digital photo frame](https://youtu.be/YDr95xaEsK4), and any other internet-connected device with a display that is capable of running Python. 9 | See it in action [here!](https://youtu.be/uRSf9xuUPog) 10 | 11 | Please note that while this project is functional, it is still a work in progress. More features and plugins are being worked on daily! 12 | 13 | **If you are interested in making a plugin,** check out the [plugin development](https://github.com/RaddedMC/SmartFrame/wiki/Plugin-development) wiki page. If you find any bugs, have suggested changes, or developed a plugin, feel free to make a pull request or contact us via any of the methods listed below. 14 | 15 | * [See a video demo of SmartFrame here!](https://youtu.be/uRSf9xuUPog) 16 | * [See a potential display setup here!](https://youtu.be/YDr95xaEsK4) 17 | 18 | ------------ 19 | 20 | # Community: 21 | 22 | Thanks to everyone who has contributed to this project! Huge shoutouts to: 23 | 24 | * [Raminh05](https://github.com/raminh05): Plugin development and Linux testing 25 | * [kelvinhall05](https://github.com/kelvinhall05): Documentation, general cringe, Linux testing, Pinephone testing 26 | * [Friendly Fire Entertainment](https://www.youtube.com/channel/UCrzKIt8myceV5tN7tgB3oYA): Advice for the original SmartFrame hardware 27 | 28 | If you'd like to contribute, come hang out in the `#smartframe-dev` and `#nerdery` channels on my [Discord](https://discord.gg/9Ms4bFw)! 29 | 30 | If you want to see my other projects, check out my other [GitHub repos](https://github.com/RaddedMC) or [YouTube channel](https://youtube.com/c/RaddedMC). You can also find me on Instagram [@RaddedMC](https://instagram.com/RaddedMC)! 31 | 32 | ------------ 33 | 34 | # To get started, check out the [project wiki](https://github.com/RaddedMC/SmartFrame/wiki) for setup instructions, plugin configuration, and examples! 35 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ansiwrap==0.8.4 2 | console-menu==0.7.1 3 | Pillow==9.2.0 4 | six==1.16.0 5 | termcolor==1.1.0 6 | textwrap3==0.9.2 7 | --------------------------------------------------------------------------------