a robot throwing pictures in buckets

How to only show portrait photos on your Raspberry Pi photo frame

This will probably be a niche article for a small audience, but that’s the fun of a blog like this: You can dive into all kinds of nerdy stuff.

Tested on a Raspberry Pi 5 with Bookworm Wayland (November 2024).

Pi3D PictureFrame allows portrait pairs, two photos in portrait mode presented side by side when the frame has a landscape orientation.

a group of people walking on a beach
a collage of a building
a collage of a beach with bicycles and people

So, I thought it would be nice to have a simple function that would automatically sort the images you add to your Pictures folder into Portrait, Landscape, and Square so you can choose to display only one.

Equally, when you mount your digital frame in portrait orientation, it would be nice to only show portrait photos. The landscape photos in portrait mode look very small.

So, here is a Python script that sorts the photos you drop into the Pictures folders and a service that starts at launch to keep the script running.

You can then choose with Home Assistant or through MQTT or HTTP commands to show only the portrait directory. It would be really cool if you could swivel your frame into portrait or landscape orientation.

The Python script for sorting

Create a script either with an editor like Sublime or with

sudo nano sort.py

and paste this text into the file

import os
import shutil
import time
from PIL import Image, UnidentifiedImageError
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

# Define paths
pictures_folder = "/path/to/Pictures"
portrait_folder = os.path.join(pictures_folder, "Portrait Orientation")
landscape_folder = os.path.join(pictures_folder, "Landscape Orientation")
square_folder = os.path.join(pictures_folder, "Square Images")

# Create folders if they don't exist
os.makedirs(portrait_folder, exist_ok=True)
os.makedirs(landscape_folder, exist_ok=True)
os.makedirs(square_folder, exist_ok=True)

# Set to track skipped files
skipped_files = set()

def is_file_complete(file_path, wait_time=1):
    """
    Check if a file is fully copied by comparing its size multiple times with a delay.
    """
    for _ in range(3):  # Check 3 times to ensure completion
        initial_size = os.path.getsize(file_path)
        time.sleep(wait_time)
        final_size = os.path.getsize(file_path)
        if initial_size == final_size:
            return True
    return False

def classify_image(file_path):
    try:
        if is_file_complete(file_path):
            with Image.open(file_path) as img:
                width, height = img.size
                if width > height:
                    destination = landscape_folder
                elif height > width:
                    destination = portrait_folder
                else:
                    destination = square_folder
                shutil.move(file_path, destination)
                print(f"Moved {file_path} to {destination}")
            # Remove from skipped files if it was previously skipped
            if file_path in skipped_files:
                skipped_files.remove(file_path)
        else:
            print(f"File {file_path} is still being copied. Adding to skipped list.")
            skipped_files.add(file_path)
    except UnidentifiedImageError:
        print(f"Cannot identify image file {file_path}. Adding to skipped list.")
        skipped_files.add(file_path)
    except Exception as e:
        print(f"Error processing {file_path}: {e}")

def classify_images_in_folder():
    for filename in os.listdir(pictures_folder):
        file_path = os.path.join(pictures_folder, filename)
        if os.path.isfile(file_path) and filename.lower().endswith(".jpg"):
            classify_image(file_path)

class ImageHandler(FileSystemEventHandler):
    def on_created(self, event):
        if event.is_directory:
            return
        if event.src_path.lower().endswith(".jpg"):
            classify_image(event.src_path)

    def on_moved(self, event):
        if not event.is_directory and event.dest_path.lower().endswith(".jpg"):
            classify_image(event.dest_path)

    def on_modified(self, event):
        if not event.is_directory and event.src_path.lower().endswith(".jpg"):
            classify_image(event.src_path)

def retry_skipped_files():
    """
    Retry classifying files that were previously skipped due to incomplete copying
    or unidentifiable errors.
    """
    for file_path in list(skipped_files):  # Iterate over a copy of the set
        if os.path.exists(file_path):
            print(f"Retrying {file_path}")
            classify_image(file_path)

if __name__ == "__main__":
    # Initial classification
    classify_images_in_folder()

    # Set up the observer
    observer = Observer()
    event_handler = ImageHandler()
    observer.schedule(event_handler, path=pictures_folder, recursive=False)
    observer.start()

    try:
        while True:
            retry_skipped_files()  # Periodically retry skipped files
            time.sleep(5)  # Adjust this sleep time as needed
    except KeyboardInterrupt:
        observer.stop()
    observer.join()

Save and close.

Make the file executable with

chmod +x /home/pi/sort.py

Install watchdog

Python has a wonderful function that triggers a command when a new file is detected in a directory.

But to make it work in the script, you first need to install it with

source venv_picframe/bin/activate
pip install pillow watchdog

Now you can try the script if it works by entering

python sort.py

Create the system service

To always have script running in the background, create a system service file for the script with

sudo nano /etc/systemd/system/sort_pictures.service

Paste the following content into the file

[Unit]
Description=Sort Pictures Service
After=network.target

[Service]
ExecStart=/home/pi/venv_picframe/bin/python /home/pi/sort.py
WorkingDirectory=/home/pi
Restart=always
User=pi

[Install]
WantedBy=multi-user.target

Save and close.

Then type (line by line) to activate the service

sudo systemctl daemon-reload

sudo systemctl enable sort_pictures.service

sudo systemctl start sort_pictures.service

Check with

sudo systemctl status sort_pictures.service

the status of the service to confirm it’s running.

Now, drop a few images into your Pictures folder.

The script should move them to the designated subdirectories according to their dimensions.

Portrait Options

Two things you can try out now.

One is to change the mounting of your frame to portrait if that is easily possible and change the settings in the Pi3D PictureFrame. Follow the instructions in “How to use your Raspberry Pi digital picture frame in portrait orientation“.

If you can’t do that, you want to try out portrait pairs by changing this line in configuration.yaml to

portrait_pairs: True

To show only the photos in portrait orientation, either change the default Pictures directory in configuration.yaml, or set the directory through Home Assistant if you have that installed.

Or you can temporarily remove the “Landscape” and “Square” directories from the main Pictures directory.

Enjoy!

Was this article helpful?


Thank you for your support and motivation.


Scroll to Top