Twitch LED Panel - Part 2: Adding Custom Animations

Heads Up: As I share my IRL adventures and tech builds here on the CabinetTerror blog, you might spot some affiliate links. If you click on those links to buy streaming gear or grab parts for your own LED screens, I may earn a small commission at absolutely no extra cost to you. It helps fund future projects, and I only recommend stuff I actually use and love. Thanks for the support!

Welcome back to Part 2 of our interactive Twitch LED Matrix build! If you followed along with Part 1, you should have a fully functioning scrolling text ticker that interacts with your Twitch chat.

But text is just the beginning. Today, we are upgrading the matrix to play full-color animated GIFs (like an 8-bit Pac-Man) whenever a specific chat command is triggered. Let’s dive in!

1. Getting the GIF onto the Pi

Before our Python script can play a GIF, we have to actually put the file onto the Raspberry Pi's hard drive. To do this, we use a free file transfer program.

  1. Download a simple 8-bit GIF from the internet. Keep it small and simple! For this guide, rename your file to exactly pacman.gif.
  2. Download and install a free SFTP client like WinSCP (for Windows) or Cyberduck (for Mac).
  3. Open WinSCP and connect to your Pi using the exact same credentials from Part 1:
    • File Protocol: SFTP
    • Host Name: twitchmatrix.local (or your Pi's IP address)
    • User Name: twitchmatrix
    • Password: The password you created in the Imager.
  4. Once connected, you will see your Pi's files on the right side of the screen. Simply drag and drop your pacman.gif file from your computer into the /home/twitchmatrix/ folder!

2. Updating the Python Code

Now we need to teach our Python script how to read an image file and play it like a movie. We will need to replace our old code with this upgraded version.

  1. Open your terminal/PowerShell and SSH into your Pi: ssh twitchmatrix@twitchmatrix.local
  2. Open your script: nano twitch_scroller.py
  3. Delete the old code (Pro tip: hold CTRL+K to delete lines fast) and paste this new Master Script:
#!/usr/bin/env python3
import time
import math
from PIL import Image, ImageSequence
from rgbmatrix import RGBMatrix, RGBMatrixOptions, graphics
import paho.mqtt.client as mqtt

# --- CONFIGURATION ---
MQTT_BROKER = "localhost"  
MQTT_TOPIC = "twitch/scroll"
LED_ROWS = 32      
LED_COLS = 64      
LED_CHAIN = 2  
GPIO_MAPPING = 'adafruit-hat'  

DEFAULT_MSG = "Waiting for Chat..." 
TEXT_HEIGHT = 22 
GREEN = graphics.Color(0, 255, 0)

current_message = DEFAULT_MSG
new_message_arrived = False
current_color = GREEN

def on_connect(client, userdata, flags, rc):
    client.subscribe(MQTT_TOPIC)

def on_message(client, userdata, msg):
    global current_message, new_message_arrived
    try:
        current_message = msg.payload.decode("utf-8").strip()
        new_message_arrived = True 
    except:
        pass

def run_matrix():
    global current_message, new_message_arrived, current_color
    options = RGBMatrixOptions()
    options.rows = LED_ROWS
    options.cols = LED_COLS
    options.chain_length = LED_CHAIN
    options.hardware_mapping = GPIO_MAPPING
    options.drop_privileges = False
    options.brightness = 40  
    
    matrix = RGBMatrix(options = options)
    offscreen_canvas = matrix.CreateFrameCanvas()

    # --- LOAD THE GIF ---
    pacman_frames = []
    try:
        pacman_gif = Image.open("/home/twitchmatrix/pacman.gif")
        for frame in ImageSequence.Iterator(pacman_gif):
            frame = frame.convert('RGB')
            # The 'resize' command stretches it across both panels!
            frame = frame.resize((offscreen_canvas.width, 32), Image.NEAREST)
            pacman_frames.append(frame)
    except Exception as e:
        print("Could not load pacman gif:", e)
    
    client = mqtt.Client()
    client.on_connect = on_connect
    client.on_message = on_message
    
    try:
        client.connect(MQTT_BROKER, 1883, 60)
        client.loop_start() 
    except:
        print("MQTT Connection Failed")

    pos = offscreen_canvas.width
    is_scrolling_custom = False
    hue = 0 

    while True:
        if new_message_arrived:
            # --- TRIGGER THE ANIMATION ---
            if current_message == "!pacman" and pacman_frames:
                end_time = time.time() + 5  # Play for 5 seconds
                while time.time() < end_time:
                    for frame in pacman_frames:
                        offscreen_canvas.Clear()
                        x_offset = (offscreen_canvas.width - frame.width) // 2
                        y_offset = (offscreen_canvas.height - frame.height) // 2
                        offscreen_canvas.SetImage(frame, x_offset, y_offset)
                        offscreen_canvas = matrix.SwapOnVSync(offscreen_canvas)
                        time.sleep(0.1) # Speed of the animation
                
                # Reset to default text when done
                current_message = DEFAULT_MSG
                new_message_arrived = False
                pos = offscreen_canvas.width
                is_scrolling_custom = False
                continue

            elif current_message != DEFAULT_MSG:
                pos = offscreen_canvas.width
                is_scrolling_custom = True 
                new_message_arrived = False 

        offscreen_canvas.Clear()
        
        if is_scrolling_custom:
            r = int(127 * math.sin(hue) + 128)
            g = int(127 * math.sin(hue + 2) + 128)
            b = int(127 * math.sin(hue + 4) + 128)
            current_color = graphics.Color(r, g, b)
            hue += 0.1 
        else:
            current_color = GREEN

        length = graphics.DrawText(offscreen_canvas, font, pos, TEXT_HEIGHT, current_color, current_message)
        pos -= 1

        if (pos + length < 0):
            if is_scrolling_custom:
                current_message = DEFAULT_MSG
                is_scrolling_custom = False
            pos = offscreen_canvas.width

        time.sleep(0.03) 
        offscreen_canvas = matrix.SwapOnVSync(offscreen_canvas)

if __name__ == "__main__":
    font = graphics.Font()
    font.LoadFont("/home/twitchmatrix/rpi-rgb-led-matrix/fonts/7x13.bdf")
    run_matrix()

Don't forget to change that second to last line of code to your username (delete twitchmatrix and replace with your username)

Save it by pressing CTRL+X, then Y, then ENTER.

Restart the matrix service so the changes take effect immediately by running:
sudo systemctl restart twitch_led.service

3. Linking the Animation to Lumia Stream

The code is specifically looking for the exact phrase !pacman to trigger the GIF. Let's make a chat command in Lumia Stream to send that phrase!

  1. Open Lumia Stream on your PC.
  2. Go to Commands > Chat Commands > Add Command.
  3. Name the command pacman.
  4. Go to the Lumia Actions tab, click Add Action, and choose MQTT.
  5. Fill out the box exactly like this:
    • Topic: twitch/scroll
    • Value: !pacman
  6. Click Save, then click the Refresh button on your Lumia Dashboard to lock it in.

The Final Test!

Go to your Twitch chat and type !pacman. Your LED sign should instantly drop the scrolling text and display your animated GIF across the boards for 5 seconds!

Comments