🎬 Star Wars Media Looper for Raspberry Pi 4

Project Overview: This comprehensive guide will help you create a Docker-based media looper that continuously displays Star Wars pictures and video clips on your Raspberry Pi 4 with a 5-inch screen. The application runs in fullscreen mode and automatically cycles through all your media files.

📋 Table of Contents

🔧 System Requirements

📁 Project Structure

Directory Layout

starwars-looper/ ├── Dockerfile ├── docker-compose.yml ├── app/ │ ├── looper.py │ └── requirements.txt └── media/ ├── images/ │ ├── image1.jpg │ ├── image2.png │ └── ... └── videos/ ├── clip1.mp4 ├── clip2.mov └── ...

⚙️ Initial Setup - Raspberry Pi Configuration

Step 1: Update Your System

First, ensure your Raspberry Pi is up to date:

sudo apt-get update sudo apt-get upgrade -y

Step 2: Install Docker

Download and install Docker using the official installation script:

curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker $USER
Note: After adding your user to the Docker group, you need to log out and log back in for the changes to take effect.

Step 3: Install Docker Compose

sudo apt-get install -y docker-compose

Step 4: Enable X Server Access for Docker

Allow Docker containers to access the display:

xhost +local:docker echo "xhost +local:docker" >> ~/.bashrc echo "export DISPLAY=:0" >> ~/.bashrc source ~/.bashrc

Step 5: Create Project Directory

mkdir -p ~/starwars-looper/app mkdir -p ~/starwars-looper/media cd ~/starwars-looper

📄 Project Files

File 1: Dockerfile

Dockerfile
FROM balenalib/raspberry-pi-debian:bullseye # Install dependencies RUN apt-get update && apt-get install -y \ python3 \ python3-pip \ python3-opencv \ libglib2.0-0 \ libsm6 \ libxext6 \ libxrender-dev \ libgomp1 \ libatlas-base-dev \ libavcodec-dev \ libavformat-dev \ libswscale-dev \ libv4l-dev \ libgtk-3-0 \ libqt5gui5 \ libqt5test5 \ x11-apps \ && rm -rf /var/lib/apt/lists/* # Set working directory WORKDIR /app # Copy requirements and install Python packages COPY app/requirements.txt . RUN pip3 install --no-cache-dir -r requirements.txt # Copy application COPY app/ . # Run the looper CMD ["python3", "looper.py"]

File 2: docker-compose.yml

docker-compose.yml
version: '3.8' services: starwars-looper: build: . container_name: starwars-looper privileged: true restart: unless-stopped environment: - DISPLAY=:0 - MEDIA_PATH=/media - IMAGE_DURATION=5 - SHUFFLE=true volumes: - /tmp/.X11-unix:/tmp/.X11-unix - ./media:/media:ro devices: - /dev/vchiq:/dev/vchiq network_mode: host

File 3: app/requirements.txt

app/requirements.txt
opencv-python==4.8.1.78 numpy==1.24.3 pillow==10.0.0

File 4: app/looper.py

app/looper.py
#!/usr/bin/env python3 import cv2 import os import time import random from pathlib import Path # Configuration MEDIA_PATH = os.getenv('MEDIA_PATH', '/media') IMAGE_DURATION = int(os.getenv('IMAGE_DURATION', '5')) SHUFFLE = os.getenv('SHUFFLE', 'true').lower() == 'true' SUPPORTED_IMAGES = {'.jpg', '.jpeg', '.png', '.bmp', '.gif'} SUPPORTED_VIDEOS = {'.mp4', '.avi', '.mov', '.mkv', '.m4v'} class MediaLooper: def __init__(self): self.media_files = [] self.load_media_files() def load_media_files(self): """Load all media files from the media directory""" print(f"Loading media files from {MEDIA_PATH}...") if not os.path.exists(MEDIA_PATH): print(f"Error: Media path {MEDIA_PATH} does not exist!") return for root, dirs, files in os.walk(MEDIA_PATH): for file in files: ext = Path(file).suffix.lower() if ext in SUPPORTED_IMAGES or ext in SUPPORTED_VIDEOS: full_path = os.path.join(root, file) self.media_files.append({ 'path': full_path, 'type': 'image' if ext in SUPPORTED_IMAGES else 'video', 'name': file }) if SHUFFLE: random.shuffle(self.media_files) print(f"Found {len(self.media_files)} media files") def display_image(self, image_path): """Display an image for a specified duration""" print(f"Displaying image: {os.path.basename(image_path)}") img = cv2.imread(image_path) if img is None: print(f"Error loading image: {image_path}") return cv2.namedWindow('Star Wars Looper', cv2.WND_PROP_FULLSCREEN) cv2.setWindowProperty('Star Wars Looper', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN) screen_height, screen_width = 480, 800 img_height, img_width = img.shape[:2] scale = min(screen_width / img_width, screen_height / img_height) new_width = int(img_width * scale) new_height = int(img_height * scale) resized_img = cv2.resize(img, (new_width, new_height)) canvas = cv2.copyMakeBorder( resized_img, (screen_height - new_height) // 2, (screen_height - new_height + 1) // 2, (screen_width - new_width) // 2, (screen_width - new_width + 1) // 2, cv2.BORDER_CONSTANT, value=[0, 0, 0] ) cv2.imshow('Star Wars Looper', canvas) cv2.waitKey(IMAGE_DURATION * 1000) def display_video(self, video_path): """Display a video clip""" print(f"Playing video: {os.path.basename(video_path)}") cap = cv2.VideoCapture(video_path) if not cap.isOpened(): print(f"Error opening video: {video_path}") return cv2.namedWindow('Star Wars Looper', cv2.WND_PROP_FULLSCREEN) cv2.setWindowProperty('Star Wars Looper', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN) fps = cap.get(cv2.CAP_PROP_FPS) delay = int(1000 / fps) if fps > 0 else 30 while cap.isOpened(): ret, frame = cap.read() if not ret: break screen_height, screen_width = 480, 800 frame_height, frame_width = frame.shape[:2] scale = min(screen_width / frame_width, screen_height / frame_height) new_width = int(frame_width * scale) new_height = int(frame_height * scale) resized_frame = cv2.resize(frame, (new_width, new_height)) canvas = cv2.copyMakeBorder( resized_frame, (screen_height - new_height) // 2, (screen_height - new_height + 1) // 2, (screen_width - new_width) // 2, (screen_width - new_width + 1) // 2, cv2.BORDER_CONSTANT, value=[0, 0, 0] ) cv2.imshow('Star Wars Looper', canvas) if cv2.waitKey(delay) & 0xFF == ord('q'): break cap.release() def run(self): """Main loop to display media files""" if not self.media_files: print("No media files found. Exiting...") return print("Starting Star Wars media looper...") print("Press 'q' to quit") try: while True: for media in self.media_files: if media['type'] == 'image': self.display_image(media['path']) else: self.display_video(media['path']) key = cv2.waitKey(1) if key & 0xFF == ord('q'): raise KeyboardInterrupt if SHUFFLE: random.shuffle(self.media_files) except KeyboardInterrupt: print("\nStopping looper...") finally: cv2.destroyAllWindows() if __name__ == "__main__": looper = MediaLooper() looper.run()

🚀 Installation Steps

Installation Process Flow

1. Create Project Files
2. Add Media Files
3. Build Docker Image
4. Run Container
5. Verify Display

Step 1: Create All Required Files

Create the Dockerfile in the project root:

nano ~/starwars-looper/Dockerfile

Copy the Dockerfile content from above, save (Ctrl+O, Enter), and exit (Ctrl+X).

Create the docker-compose.yml file:

nano ~/starwars-looper/docker-compose.yml

Copy the docker-compose.yml content from above, save and exit.

Create the requirements.txt file:

nano ~/starwars-looper/app/requirements.txt

Copy the requirements.txt content from above, save and exit.

Create the looper.py file:

nano ~/starwars-looper/app/looper.py

Copy the looper.py content from above, save and exit.

Make the Python script executable:

chmod +x ~/starwars-looper/app/looper.py

Step 2: Add Your Media Files

Copy your Star Wars images and videos to the media folder. You can organize them in subfolders if desired:

cp /path/to/your/starwars/images/*.jpg ~/starwars-looper/media/ cp /path/to/your/starwars/videos/*.mp4 ~/starwars-looper/media/
💡 TIP: You can use USB drives or SCP to transfer media files to your Raspberry Pi.

Example using USB drive:

sudo mount /dev/sda1 /mnt/usb cp /mnt/usb/starwars/* ~/starwars-looper/media/ sudo umount /mnt/usb

Step 3: Build the Docker Image

Navigate to the project directory and build the image:

cd ~/starwars-looper docker-compose build
⚠️ Important: The initial build may take 10-20 minutes on Raspberry Pi 4 as it downloads and compiles all dependencies. Be patient!

Step 4: Run the Container

Start the media looper in detached mode:

docker-compose up -d

Step 5: Verify Operation

Check the logs to ensure everything is working:

docker-compose logs -f

You should see output like:

Loading media files from /media... Found 15 media files Starting Star Wars media looper... Displaying image: millennium-falcon.jpg Playing video: death-star-trench.mp4

Press Ctrl+C to exit the log view (this won't stop the container).

⚙️ Configuration Options

Environment Variable Default Value Description
MEDIA_PATH /media Path to the media folder inside the container
IMAGE_DURATION 5 Number of seconds to display each image
SHUFFLE true Randomly shuffle media files (true/false)

Customizing Settings

To change configuration, edit the docker-compose.yml file:

nano ~/starwars-looper/docker-compose.yml

Example: Change image duration to 10 seconds and disable shuffle:

environment: - DISPLAY=:0 - MEDIA_PATH=/media - IMAGE_DURATION=10 - SHUFFLE=false

After making changes, restart the container:

docker-compose down docker-compose up -d

Screen Resolution Adjustment

If your 5-inch screen has a different resolution than 800x480, edit the looper.py file:

nano ~/starwars-looper/app/looper.py

Find these lines and adjust the values:

screen_height, screen_width = 480, 800 # Adjust to your screen resolution
Common 5-inch screen resolutions:

🎮 Usage Commands

Starting the Looper

cd ~/starwars-looper docker-compose up -d

Stopping the Looper

docker-compose down

Viewing Logs

docker-compose logs -f

Restarting the Looper

docker-compose restart

Checking Container Status

docker ps

Adding New Media Files

Copy new files to the media folder:

cp /path/to/new/files/* ~/starwars-looper/media/

Then restart the container:

docker-compose restart

Removing the Project

docker-compose down docker rmi starwars-looper_starwars-looper rm -rf ~/starwars-looper

🔄 Auto-Start on Boot

To make the looper start automatically when Raspberry Pi boots:

Method 1: Using systemd (Recommended)

Create a systemd service file:

sudo nano /etc/systemd/system/starwars-looper.service

Add the following content:

[Unit] Description=Star Wars Media Looper After=docker.service Requires=docker.service [Service] Type=oneshot RemainAfterExit=yes WorkingDirectory=/home/pi/starwars-looper ExecStart=/usr/bin/docker-compose up -d ExecStop=/usr/bin/docker-compose down User=pi [Install] WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload sudo systemctl enable starwars-looper.service sudo systemctl start starwars-looper.service

Method 2: Using crontab

crontab -e

Add this line at the end:

@reboot sleep 30 && cd /home/pi/starwars-looper && /usr/bin/docker-compose up -d

🔧 Troubleshooting

Problem: "No media files found" Error

Symptoms: Container starts but shows "Found 0 media files"

Solution:

1. Verify media files exist in the correct location:

ls -la ~/starwars-looper/media/

2. Check file permissions:

chmod -R 755 ~/starwars-looper/media/

3. Verify supported file formats (jpg, jpeg, png, bmp, gif, mp4, avi, mov, mkv, m4v)

4. Restart the container:

docker-compose restart

Problem: Display Not Showing

Symptoms: Container runs but nothing appears on screen

Solution:

1. Check DISPLAY environment variable:

echo $DISPLAY

2. Grant X server access:

xhost +local:docker

3. Verify X11 socket permissions:

ls -la /tmp/.X11-unix/

4. Test X server access from container:

docker exec -it starwars-looper env DISPLAY=:0 xeyes

Problem: Video Stuttering or Lag

Symptoms: Videos play but with poor performance

Solution:

1. Reduce video resolution. Convert videos using ffmpeg:

sudo apt-get install ffmpeg ffmpeg -i input.mp4 -vf scale=800:480 -c:v libx264 -crf 23 output.mp4

2. Increase GPU memory allocation:

sudo nano /boot/config.txt

Add or modify:

gpu_mem=256
sudo reboot

Problem: Container Won't Start

Symptoms: Docker-compose fails to start container

Solution:

1. Check Docker service status:

sudo systemctl status docker

2. View detailed error logs:

docker-compose logs

3. Verify Docker Compose file syntax:

docker-compose config

4. Rebuild the image:

docker-compose build --no-cache

Problem: Image/Video Not Filling Screen

Symptoms: Content appears too small or has large borders

Solution:

1. Check your actual screen resolution:

xrandr | grep '*'

2. Update screen dimensions in looper.py to match your display

3. Rebuild and restart:

docker-compose down docker-compose build docker-compose up -d

Problem: Permission Denied Errors

Symptoms: Docker commands require sudo

Solution:

1. Add user to docker group:

sudo usermod -aG docker $USER

2. Log out and log back in, or run:

newgrp docker

3. Verify group membership:

groups

📊 Performance Optimization Tips

1. Optimize Media Files

2. System Configuration

3. Docker Optimization

🎯 Advanced Customization

Adding Background Music

You can extend the looper to play Star Wars music in the background by installing pygame and modifying looper.py:

Add 'pygame' to requirements.txt

Adding Transition Effects

Implement fade-in/fade-out effects between images by modifying the display_image function in looper.py to use OpenCV's blending functions.

Remote Control via Web Interface

You could add a Flask web server to control playback remotely from your phone or computer.

Multiple Display Support

Run multiple instances with different DISPLAY values to show on multiple screens simultaneously.

📝 Supported File Formats

Type Supported Formats Recommended Format
Images .jpg, .jpeg, .png, .bmp, .gif JPG (best compression/quality ratio)
Videos .mp4, .avi, .mov, .mkv, .m4v MP4 with H.264 codec

🆘 Getting Help

Useful Commands for Diagnostics

Check Docker version:

docker --version

Check Docker Compose version:

docker-compose --version

List all containers:

docker ps -a

View container resource usage:

docker stats starwars-looper

Access container shell for debugging:

docker exec -it starwars-looper /bin/bash

✅ Final Checklist

Before running your project, ensure:

🎬 Expected Output

When everything is working correctly, you should see:

⚠️ Important Notes


May the Force be with your Raspberry Pi! 🚀

Created for Raspberry Pi 4 with Docker • Last updated: March 2026