Turn your TV into a picture frame

Posted by mantonel on July 6, 2016. Last update on October 16, 2016.
Python Raspberry Pi

Introduction

I set up my Raspberry Pi as decribed in the first part of this tutorial.

The aim of this tutorial is to turn your tv into a big picture frame when you don't use it (you'd better not watch too much shitty programs on TV).

To do this we are going to need a Raspberry Pi, an HDMI cable, an external hard drive or a NAS where your pictures are stored and obviously a TV (or any other screen of course)!

For this tutorial I am going to get my pictures from a NAS but you can also get them from an external hard drive plugged into your Raspberry Pi.

Get your pictures from your NAS/NFS server (skip this part if you use an external hard drive)

I'm going to mount the remote folder "/volume1/photo" from my NAS into the folder "/nas/photo" on my Raspberry Pi.

First check if you can mount the remote folder. You need to enable and start nfs (by default the nfs service is not running) and then mount the remote folder:

sudo update-rc.d rpcbind enable
sudo /etc/init.d/rpcbind start
sudo mount 192.168.1.10:/volume1/photo /nas/photo

When the folder is mounted, open the file /etc/fstab and add the following line at the end (this is going to mount the remote folder at startup):

192.168.1.10:/volume1/photo /nas/photo nfs ro 0 0

Don't forget to create the folder "/nas/photo" and give it permissions then restart the Raspberry Pi:

sudo restart

Now you should see your pictures inside the shared folder "/nas/photo" on your Raspberry Pi.

Display you pictures using pygame

For this tutorial, in order to make it simple, I downloaded 4 full HD pictures into the folder "/photos":

  • picture1.jpg
  • picture2.jpg
  • picture3.jpg
  • picture4.jpg

Install pygame:

sudo python3 -m pip install pygame

The Python script is going to work like that:

  • initialize the rendering surface;
  • look for pictures into the folder "/photos";
  • every 2 seconds, load a picture and display it.

Here is the code:

#!/usr/bin/env python3

import pygame
from time import sleep
from os import walk
from random import shuffle

PICTURES_PATH = "/photos"
DISPLAYING_TIME = 2

# Set pygame parameters and get the surface used to render
pygame.init()
rendering_surface = pygame.display.set_mode((0,0), pygame.FULLSCREEN, 24)
pygame.mouse.set_visible(False)

# Here we get the file names of pictures
file_paths = []
for root, dirs, files in walk(PICTURES_PATH):
    for file in files:
        full_path = root + "/" + file
        file_paths.append(full_path)

shuffle(file_paths)
index = 0

# Rendering loop
while True:
    image = pygame.image.load(file_paths[index])

    rendering_surface.blit(image, (0,0))

    sleep(DISPLAYING_TIME)
    pygame.display.update()

    index = (index+1)%(len(file_paths))

If you are executing the script from a remote terminal, do not forget to execute this command line before the script:

export DISPLAY=:0.0

Optionnal stuff

Here are some ideas to improve this script:

  • resize the pictures if they are too small or too big (avoid doing it if they almost match the size);
  • do not display too small pictures to avoid poor quality;
  • filter the files by name or by folder if you don't want specific pictures.

Full example

Here is a bigger sample of the code I use (with the improvements listed above):

#!/usr/bin/env python3

import pygame, re
from time import sleep
from os import walk
from random import shuffle

FOLDER_PATH = "/nas/photo"
DISPLAYING_TIME = 5
# If the size of the picture is < MIN_IMAGE_RATIO then the image is not displayed to avoid poor quality
MIN_IMAGE_RATIO = 0.5
# If the size of the picture is between screenSize*(1+MIN_RATIO_GAP_TO_RESIZE) and screenSize*(1-MIN_RATIO_GAP_TO_RESIZE) then it is not resized
MIN_RATIO_GAP_TO_RESIZE = 0.2
# Background color
BLACK = (0,0,0,255)
FILE_NAME_PATTERN = ".*[.](jpg|JPG|png|PNG|jpeg|JPEG|bmp|BMP|gif|GIF)$"
FILE_NAME_EXCLUSION = "@eaDir"

def init():
    """Create the rendering surface and return the rendering surface, screen width and screen height."""
    pygame.init()
    print("Init done.")
    rendering_surface = pygame.display.set_mode((0,0), pygame.FULLSCREEN, 24)
    print("Rendering surface created.")
    pygame.mouse.set_visible(False)

    screen_width = pygame.display.Info().current_w
    screen_height = pygame.display.Info().current_h

    print("Display size : ", str(screen_width), "*", str(screen_height) + ".")
    return rendering_surface, screen_width, screen_height

def get_images_from_folder(folder_path_p, file_name_pattern_p, file_name_exclusion_p):
    """Look for all the pictures into the given folder and return them."""
    images_paths = []
    file_name_pattern = re.compile(file_name_pattern_p)
    file_name_exclusion = re.compile(file_name_exclusion_p)

    for root, dirs, files in walk(folder_path_p):
        for file in files:
            full_path = root + "/" + file
            match = file_name_pattern.match(full_path)
            search = file_name_exclusion.search(full_path)
            if match and not search:
                print("Matching file : ", full_path)
                images_paths.append(full_path)
    print("Files parsed.")
    return images_paths

def render(rendering_surface_p, image_list_p, min_image_width_p, min_image_height_p, screen_width_p, screen_height_p, displaying_time_p):
    """Render the images from image_list_p into the rendering_surface_p, each image is displayed displaying_time_p seconds."""
    index = 0
    index = draw_image_from_list(rendering_surface_p, image_list_p, index, min_image_width_p, min_image_height_p, screen_width_p, screen_height_p, 0)

    # Rendering loop
    while True:
        index = (index+1)%(len(image_list_p))
        index = draw_image_from_list(rendering_surface_p, image_list_p, index, min_image_width_p, min_image_height_p, screen_width_p, screen_height_p, displaying_time_p)

def draw_image_from_list(rendering_surface_p, img_list_p, index_p, min_image_width_p, min_image_height_p, screen_width_p, screen_height_p, sleep_time_p):
    """Starting from img_list_p[index_p], look for the first image into the list meeting the size requirements and then draw it on rendering_surface_p."""
    image = load_image_from_path(img_list_p[index_p])
    # Remove too small or too large images
    while select_image(image, min_image_width_p, min_image_height_p, None, None) == None:
        print("Image rejected.")
        index_p = (index_p+1)%(len(img_list_p))
        image = load_image_from_path(img_list_p[index_p])

    draw_image(rendering_surface_p, image, screen_width_p, screen_height_p, sleep_time_p)
    return index_p

def load_image_from_path(path_p):
    """Return the image loaded from the given path, none if the image can't be loaded."""
    print("Trying to load image from FILE : ", path_p)
    try:
        return pygame.image.load(path_p)
    except :
        print("Can't load ", path_p, ".")
        return None

def select_image(image_p, min_width_p, min_height_p, max_width_p, max_height_p):
    """Return the image if the image is inside the bounds, none otherwise."""
    if image_p == None:
        return None
    image_width, image_height = image_p.get_rect().size
    print("Image width : ", image_width, ", height : ", image_height)
    if (min_width_p == None or image_width >= min_width_p) and (min_height_p == None or image_height >= min_height_p) and (max_width_p == None or image_width <= max_width_p) and (max_height_p == None or image_height <= max_height_p):
        return image_p
    else:
        return None

def draw_image(rendering_surface_p, image_p, screen_width_p, screen_height_p, sleep_time_p):
    """"Draw the given image (resized if needed) and wait sleep_time_p seconds."""
    img_width, img_height = image_p.get_rect().size
    rendering_surface_p.fill(BLACK)
    ratio = min (screen_width_p/img_width, screen_height_p/img_height)

    # It is not resized if the size almost matches
    if ratio > 1 + MIN_RATIO_GAP_TO_RESIZE or ratio < 1 - MIN_RATIO_GAP_TO_RESIZE:
        scaled_img = pygame.transform.smoothscale(image_p, (int(img_width*ratio), int(img_height*ratio)))
        scaled_img_width, scaled_img_height = scaled_img.get_rect().size
        print("Image resized : ", str(img_width), "*", str(img_height), " to ", str(scaled_img_width), "*", str(scaled_img_height), ".")
        rendering_surface_p.blit(scaled_img, ((screen_width_p - scaled_img_width)//2, (screen_height_p - scaled_img_height)//2))
    else:
        print("Image not resized : ", str(img_width), "*", str(img_height), ".")
        rendering_surface_p.blit(image_p, ((screen_width_p - img_width)//2, (screen_height_p - img_height)//2))

    sleep(sleep_time_p)
    print("Draw.")
    pygame.display.update()

########
# main #
########

rendering_surface, screen_width, screen_height = init()

# The images smaller than half the size of the screen are not going to be used (to avoid poor quality)
min_image_width = screen_width*MIN_IMAGE_RATIO
min_image_height = screen_height*MIN_IMAGE_RATIO

# This list contains the full path of all the selected images
paths_list = get_images_from_folder(FOLDER_PATH, FILE_NAME_PATTERN, FILE_NAME_EXCLUSION)

if len(paths_list) < 2:
    print("Not enough images found (<2).")
    quit()

# Shuffle the list to display random pictures
shuffle(paths_list)

print("Start drawing pictures...")
render(rendering_surface, paths_list, min_image_width, min_image_height, screen_width, screen_height, DISPLAYING_TIME)


0 Comment