Skip to content

Commit

Permalink
Add Image Player (#134)
Browse files Browse the repository at this point in the history
v1.0.10
  • Loading branch information
tofuSCHNITZEL authored Dec 21, 2021
1 parent 4173821 commit 067826d
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Adafruit_Video_Looper/hello_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,6 @@ def can_loop_count():
return True


def create_player(config):
def create_player(config, **kwargs):
"""Create new video player based on hello_video."""
return HelloVideoPlayer(config)
113 changes: 113 additions & 0 deletions Adafruit_Video_Looper/image_player.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Author: Tobias Perschon
# License: GNU GPLv2, see LICENSE.txt
import os, pygame
from time import monotonic

class ImagePlayer:

def __init__(self, config, screen, bgimage):
"""Create an instance of an image player uses pygame to display static images.
"""
self._load_config(config)
self._screen = screen
self._loop = 0
self._startTime = 0
self._bgimage = bgimage

def _load_config(self, config):
self._extensions = config.get('image_player', 'extensions') \
.translate(str.maketrans('', '', ' \t\r\n.')) \
.split(',')
self._duration = config.getint('image_player', 'duration')
self._size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
self._bgcolor = list(map(int, config.get('video_looper', 'bgcolor')
.translate(str.maketrans('','', ','))
.split()))
self._scale = config.getboolean('image_player', 'scale')
self._center = config.getboolean('image_player', 'center')
self._wait_time = config.getint('video_looper', 'wait_time')

def supported_extensions(self):
"""Return list of supported file extensions."""
return self._extensions

def play(self, image, loop=None, **kwargs):
"""Display the provided image file."""
if loop is None:
self._loop = image.repeats
else:
self._loop = loop
if self._loop == 0:
self._loop = 1

imagepath = image.filename

if imagepath != "" and os.path.isfile(imagepath):
self._blank_screen(False)
pyimage = pygame.image.load(imagepath)
image_x = 0
image_y = 0
screen_w, screen_h = self._size
image_w, image_h = pyimage.get_size()
new_image_w, new_image_h = pyimage.get_size()
screen_aspect_ratio = screen_w / screen_h
photo_aspect_ratio = image_w / image_h

if self._scale:
if screen_aspect_ratio < photo_aspect_ratio: # Width is binding
new_image_w = screen_w
new_image_h = int(new_image_w / photo_aspect_ratio)
pyimage = pygame.transform.scale(pyimage, (new_image_w, new_image_h))
elif screen_aspect_ratio > photo_aspect_ratio: # Height is binding
new_image_h = screen_h
new_image_w = int(new_image_h * photo_aspect_ratio)
pyimage = pygame.transform.scale(pyimage, (new_image_w, new_image_h))
else: # Images have the same aspect ratio
pyimage = pygame.transform.scale(pyimage, (screen_w, screen_h))

if self._center:
if screen_aspect_ratio < photo_aspect_ratio:
image_y = (screen_h - new_image_h) // 2
elif screen_aspect_ratio > photo_aspect_ratio:
image_x = (screen_w - new_image_w) // 2

self._screen.blit(pyimage, (image_x, image_y))
pygame.display.flip()
#future todo: crossfade, ken burns possbile?
#future todo: maybe preload images and/or create pygame image dict

self._startTime = monotonic()

def is_playing(self):
"""Here we need to compare for how long the image was displayed, also taking the in and set playing to false if time is up also"""
if self._loop <= -1: #loop one image = play forever
return True

playing = (monotonic() - self._startTime) < self._duration*self._loop

if not playing and self._wait_time > 0: #only refresh background if we wait between images
self._blank_screen()

return playing

def stop(self, block_timeout_sec=0):
"""Stop the image display."""
self._blank_screen()
self._startTime = -monotonic()

def _blank_screen(self, flip=True):
"""Render a blank screen filled with the background color and optional the background image."""
self._screen.fill(self._bgcolor)
if self._bgimage[0] is not None:
self._screen.blit(self._bgimage[0], (self._bgimage[1], self._bgimage[2]))
if(flip):
pygame.display.flip()

@staticmethod
def can_loop_count():
return True


def create_player(config, **kwargs):
"""Create new image player."""
return ImagePlayer(config, screen=kwargs['screen'], bgimage=kwargs['bgimage'])
4 changes: 2 additions & 2 deletions Adafruit_Video_Looper/omxplayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def play(self, movie, loop=None, vol=0):
args = ['omxplayer']
args.extend(['-o', self._sound]) # Add sound arguments.
args.extend(self._extra_args) # Add extra arguments from config.
if vol is not 0:
if vol != 0:
args.extend(['--vol', str(vol)])
if loop is None:
loop = movie.repeats
Expand Down Expand Up @@ -108,6 +108,6 @@ def can_loop_count():
return False


def create_player(config):
def create_player(config, **kwargs):
"""Create new video player based on omxplayer."""
return OMXPlayer(config)
1 change: 0 additions & 1 deletion Adafruit_Video_Looper/usb_drive_copymode.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ def _load_config(self, config):
self._copyloader = config.getboolean('copymode', 'copyloader')
self._password = config.get('copymode', 'password')

#needs to be changed to a more generic approach to support other players
self._extensions = '|'.join(config.get(self._config.get('video_looper', 'video_player'), 'extensions') \
.translate(str.maketrans('','', ' \t\r\n.')) \
.split(','))
Expand Down
41 changes: 32 additions & 9 deletions Adafruit_Video_Looper/video_looper.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def __init__(self, config_path):
pygame.mouse.set_visible(False)
self._screen = pygame.display.set_mode((0,0), pygame.FULLSCREEN | pygame.NOFRAME)
self._size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
self._bgimage = self._load_bgimage()
self._bgimage = self._load_bgimage() #a tupple with pyimage, xpos, ypos
self._blank_screen()
# Load configured video player and file reader modules.
self._player = self._load_player()
Expand Down Expand Up @@ -116,7 +116,7 @@ def _print(self, message):
def _load_player(self):
"""Load the configured video player and return an instance of it."""
module = self._config.get('video_looper', 'video_player')
return importlib.import_module('.' + module, 'Adafruit_Video_Looper').create_player(self._config)
return importlib.import_module('.' + module, 'Adafruit_Video_Looper').create_player(self._config, screen=self._screen, bgimage=self._bgimage)

def _load_file_reader(self):
"""Load the configured file reader and return an instance of it."""
Expand All @@ -126,13 +126,37 @@ def _load_file_reader(self):
def _load_bgimage(self):
"""Load the configured background image and return an instance of it."""
image = None
image_x = 0
image_y = 0

if self._config.has_option('video_looper', 'bgimage'):
imagepath = self._config.get('video_looper', 'bgimage')
if imagepath != "" and os.path.isfile(imagepath):
self._print('Using ' + str(imagepath) + ' as a background')
image = pygame.image.load(imagepath)
image = pygame.transform.scale(image, self._size)
return image

screen_w, screen_h = self._size
image_w, image_h = image.get_size()

screen_aspect_ratio = screen_w / screen_h
photo_aspect_ratio = image_w / image_h

if screen_aspect_ratio < photo_aspect_ratio: # Width is binding
new_image_w = screen_w
new_image_h = int(new_image_w / photo_aspect_ratio)
image = pygame.transform.scale(image, (new_image_w, new_image_h))
image_y = (screen_h - new_image_h) // 2

elif screen_aspect_ratio > photo_aspect_ratio: # Height is binding
new_image_h = screen_h
new_image_w = int(new_image_h * photo_aspect_ratio)
image = pygame.transform.scale(image, (new_image_w, new_image_h))
image_x = (screen_w - new_image_w) // 2

else: # Images have the same aspect ratio
image = pygame.transform.scale(image, (screen_w, screen_h))

return (image, image_x, image_y)

def _is_number(self, s):
try:
Expand Down Expand Up @@ -226,12 +250,11 @@ def _build_playlist_from_all_files(self):
return Playlist(sorted(movies))

def _blank_screen(self):
"""Render a blank screen filled with the background color."""
"""Render a blank screen filled with the background color and optional the background image."""
self._screen.fill(self._bgcolor)
if self._bgimage is not None:
rect = self._bgimage.get_rect()
self._screen.blit(self._bgimage, rect)
pygame.display.update()
if self._bgimage[0] is not None:
self._screen.blit(self._bgimage[0], (self._bgimage[1], self._bgimage[2]))
pygame.display.flip()

def _render_text(self, message, font=None):
"""Draw the provided message and return as pygame surface of it rendered
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ You can download it from here: https://www.raspberrypi.com/software/operating-sy
There are also pre-compiled versions available from: https://videolooper.de/ (but they might not contain the latest version of pi_video_looper)

## Changelog
#### new in v1.0.10
- NEW PLAYER: "Image Player" (beta)
The new player can display images instead of videos (slideshow).
Display duration and other options can be controlled via "image_player" section in ini
All other settings, like background image, color, wait time, copy mode, keyboard shortcuts, etc. should work as expected
Currently tested formats: jpg, gif, png (others might work also - you need to adapt the extensions setting)

#### new in v1.0.9
- fixed: background image is reloaded in copymode without restart

#### new in v1.0.8
- playlist resume option
- playlist resume option
when enabled will resume last played file on restart
- console output now has a timestamp for easier event tracking
- Keyboard key for shutdown added ("p")
Expand Down
31 changes: 26 additions & 5 deletions assets/video_looper.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
# with full audio and video, but it has a small ~100ms delay between videos.
# if there is only one video omxplayer can also loop seamlessly
# hello_video is a simpler player that doesn't do audio and only plays raw H264
# streams, but loops videos seamlessly if one video is played more than once. The default is omxplayer.
# streams, but loops videos seamlessly if one video is played more than once.
# The image_player only displays images for the duration configured in this file under the "image_player" section.
# The default is omxplayer.
video_player = omxplayer
#video_player = hello_video
#video_player = image_player

# Where to find movie files. Can be either usb_drive or directory. When using
# usb_drive any USB stick inserted in to the Pi will be automatically mounted
Expand Down Expand Up @@ -62,14 +65,14 @@ keyboard_control = true
#keyboard_control = false

# Set the background to a custom image
# This image is displayed between movies
# Can potentially look broken if video resolution is smaller than display resolution
# This image is displayed between movies or images
# it image will be scaled to the display resolution and centered
# bgimage = /home/pi/loader.png
bgimage =

# Change the color of the background that is displayed behind movies (only works
# with omxplayer). Provide 3 numeric values from 0 to 255 separated by a commma
# for the red, green, and blue color value. Default is 0, 0, 0 or black.
# with omxplayer and the image_player). Provide 3 numeric values from 0 to 255 separated by a commma
# for the red, green, and blue color value. Default is 0, 0, 0 which is black.
bgcolor = 0, 0, 0

# Change the color of the foreground text that is displayed with the on screen
Expand Down Expand Up @@ -205,3 +208,21 @@ extra_args = --no-osd --audio_fifo 0.01 --video_fifo 0.01 --align center --font-
# List of supported file extensions. Must be comma separated and should not
# include the dot at the start of the extension.
extensions = h264

# image player configuration follows
[image_player]

# List of supported file extensions. Must be comma separated and should not
# include the dot at the start of the extension.
extensions = jpg, jpeg, gif, png

# Seconds an image is displayed
duration = 5

# Controls if images should be scaled to fit (shrink or enlarge) the display resolution. Default: true
scale = true
#scale = false

# Controls if images should be displayed in the center. Default: true
center = true
#center = false
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from setuptools import setup, find_packages

setup(name = 'Adafruit_Video_Looper',
version = '1.0.9',
version = '1.0.10',
author = 'Tony DiCola',
author_email = '[email protected]',
description = 'Application to turn your Raspberry Pi into a dedicated looping video playback device, good for art installations, information displays, or just playing cat videos all day.',
Expand Down

0 comments on commit 067826d

Please sign in to comment.