Skip to content

Commit a237f01

Browse files
authored
Merge pull request #3157 from FoamyGuy/pixelfed_viewer
fruit jam pixelfed photo viewer
2 parents 786602f + 1edb998 commit a237f01

File tree

1 file changed

+188
-0
lines changed
  • Fruit_Jam/Fruit_Jam_Pixelfed_Photos

1 file changed

+188
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
import gc
5+
import os
6+
import json
7+
import random
8+
import time
9+
10+
import board
11+
import busio
12+
from digitalio import DigitalInOut
13+
from displayio import TileGrid, Group, OnDiskBitmap
14+
import supervisor
15+
16+
import adafruit_connection_manager
17+
from adafruit_esp32spi import adafruit_esp32spi
18+
from adafruit_fruitjam.peripherals import request_display_config
19+
import adafruit_requests
20+
21+
# what kind of images to search for
22+
search_tag = "cats"
23+
24+
# pixelfed search API URL
25+
URL = f"https://pixelfed.social/api/v2/discover/tag?hashtag={search_tag}"
26+
27+
# small screen size
28+
request_display_config(320, 240)
29+
display = supervisor.runtime.display
30+
31+
# Get WiFi details, ensure these are setup in settings.toml
32+
ssid = os.getenv("CIRCUITPY_WIFI_SSID")
33+
password = os.getenv("CIRCUITPY_WIFI_PASSWORD")
34+
35+
# Get AIO details, ensure these are setup in settings.toml
36+
AIO_USERNAME = os.getenv("ADAFRUIT_AIO_USERNAME")
37+
AIO_KEY = os.getenv("ADAFRUIT_AIO_KEY")
38+
39+
# AIO image converter URL template
40+
IMAGE_CONVERTER_SERVICE = (
41+
f"https://io.adafruit.com/api/v2/{AIO_USERNAME}/integrations/image-formatter?"
42+
f"x-aio-key={AIO_KEY}&width={display.width}&height={display.height}&output=BMP16&url=%s"
43+
)
44+
45+
# Main group to hold visible elements
46+
main_group = Group()
47+
display.root_group = main_group
48+
49+
# try to load the previously downloaded image
50+
try:
51+
img_bmp = OnDiskBitmap("/saves/downloaded_img.bmp")
52+
img_tg = TileGrid(img_bmp, pixel_shader=img_bmp.pixel_shader)
53+
main_group.append(img_tg)
54+
except (ValueError, RuntimeError):
55+
img_tg = None
56+
57+
# Setup WIFI
58+
esp32_cs = DigitalInOut(board.ESP_CS)
59+
esp32_ready = DigitalInOut(board.ESP_BUSY)
60+
esp32_reset = DigitalInOut(board.ESP_RESET)
61+
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
62+
radio = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
63+
64+
print("Connecting to AP...")
65+
while not radio.is_connected:
66+
try:
67+
radio.connect_AP(ssid, password)
68+
except RuntimeError as e:
69+
print("could not connect to AP, retrying: ", e)
70+
continue
71+
print("Connected to", str(radio.ap_info.ssid, "utf-8"), "\tRSSI:", radio.ap_info.rssi)
72+
73+
# Initialize a requests session
74+
pool = adafruit_connection_manager.get_radio_socketpool(radio)
75+
ssl_context = adafruit_connection_manager.get_radio_ssl_context(radio)
76+
requests = adafruit_requests.Session(pool, ssl_context)
77+
78+
79+
def fetch_pixelfed_search():
80+
"""
81+
Fetch search data from the pixelfed JSON API and cache the results
82+
in /saves/pixelfed.json
83+
84+
:return: None
85+
"""
86+
with requests.get(URL) as search_response:
87+
print(f"pixelfeld search status code: {search_response.status_code}")
88+
resp_data = search_response.text
89+
if search_response.status_code == 200:
90+
with open("/saves/pixelfed.json", "w") as cache_file:
91+
cache_file.write(resp_data)
92+
print("wrote search results to /saves/pixelfed.json")
93+
94+
95+
def build_img_url_list():
96+
"""
97+
Extract a list of URLs for images from the cached search data in /saves/pixelfed.json
98+
:return: The list of URLs
99+
"""
100+
image_urls_to_show = []
101+
for i in range(len(search_results_obj["tags"])):
102+
post_obj = search_results_obj["tags"][i]
103+
for attachment in post_obj["status"]["media_attachments"]:
104+
if attachment["is_nsfw"] == 0:
105+
_img_url = attachment["preview_url"]
106+
image_urls_to_show.append(_img_url)
107+
108+
# shuffle the order of the final list
109+
randomized_image_urls = []
110+
while len(image_urls_to_show) > 0:
111+
random_image_url = random.choice(image_urls_to_show)
112+
image_urls_to_show.remove(random_image_url)
113+
randomized_image_urls.append(random_image_url)
114+
return randomized_image_urls
115+
116+
117+
# variable to store a timestamp of when we last searched
118+
last_search_time = 0
119+
120+
# try to read cached search data
121+
try:
122+
with open("/saves/pixelfed.json", "r") as cached_search_data:
123+
search_results_obj = json.loads(cached_search_data.read())
124+
125+
except OSError: # if there is no cached search data
126+
# fetch search data now
127+
fetch_pixelfed_search()
128+
# update the timestamp
129+
last_search_time = time.monotonic()
130+
# load the fetched data from the cache file
131+
with open("/saves/pixelfed.json", "r") as cached_search_data:
132+
search_results_obj = json.loads(cached_search_data.read())
133+
134+
# get the list of images to show
135+
images = build_img_url_list()
136+
137+
# main loop
138+
while True:
139+
# loop over all images in the list
140+
for img_url in images:
141+
# prepare the converter URL for the current image
142+
converter_url = IMAGE_CONVERTER_SERVICE.replace("%s", img_url)
143+
print("Fetching converted img")
144+
# attempt to download the converted image
145+
with requests.get(converter_url) as response:
146+
print("conversion status code: ", response.status_code)
147+
# if the conversion was successful
148+
if response.status_code == 200:
149+
# save the image CPSAVES
150+
with open("/saves/downloaded_img.bmp", "wb") as outfile:
151+
for chunk in response.iter_content(chunk_size=8192):
152+
outfile.write(chunk)
153+
img_download_success = True
154+
155+
# if the conversion was not successful
156+
elif response.status_code == 429: # rate limit error
157+
print("Img converter rate limit")
158+
img_download_success = False
159+
time.sleep(60)
160+
else:
161+
print(response.content)
162+
img_download_success = False
163+
time.sleep(60)
164+
165+
if img_download_success:
166+
print("Loading img")
167+
# load and show the downloaded image file
168+
try:
169+
img_bmp = OnDiskBitmap("/saves/downloaded_img.bmp")
170+
if img_tg in main_group:
171+
main_group.remove(img_tg)
172+
gc.collect()
173+
img_tg = TileGrid(img_bmp, pixel_shader=img_bmp.pixel_shader)
174+
main_group.append(img_tg)
175+
except (ValueError, RuntimeError):
176+
pass
177+
178+
# wait for 4 minutes
179+
time.sleep(60 * 4)
180+
181+
# if it has been at least an hour since the last search time
182+
if time.monotonic() - last_search_time > (60 * 60):
183+
# fetch new search data and load the list of images from it
184+
fetch_pixelfed_search()
185+
last_search_time = time.monotonic()
186+
with open("/saves/pixelfed.json", "r") as cached_search_data:
187+
search_results_obj = json.loads(cached_search_data.read())
188+
images = build_img_url_list()

0 commit comments

Comments
 (0)