Pygame slideshow delay anormally long - python

I'm setting up a Slideshow system mixing images and videos, from a directory.
I'm using a Raspberry Pi B, pygame and vlc.
I didn't install X so everything happens in framebuffer.
My actual code is working but :
The 4 seconds delay is not respected. The image is displayed +- 11 seconds.
One of the images witch has nothing particular, is displayed much longer, +- 1m30. (my real problem)
I tried a bash script with fbi, fim, vlc without suitable result. The closest was with vlc but it takes too long to render an image in framebuffer.
I'm quite new to pygame. Here is the code:
import pygame
import sys
import time
import vlc
import os
filesdir = '/home/pi/SMBmount/'
pygame.init()
size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
black = 0, 0, 0
screen = pygame.display.set_mode(size)
while True:
# For every file in filesdir :
for filename in os.listdir(filesdir):
filenamelower = filename.lower()
# If image:
if filenamelower.endswith('.png') or filenamelower.endswith('.jpg') or filenamelower.endswith('.jpeg'):
fullname = filesdir + filename
img = pygame.image.load(fullname)
img = pygame.transform.scale(img, size)
imgrect = img.get_rect()
screen.fill(black)
screen.blit(img, imgrect)
pygame.mouse.set_visible(False)
pygame.display.flip()
time.sleep(4)
# Elif video:
elif filenamelower.endswith('.mp4') or filenamelower.endswith('.mkv') or filenamelower.endswith('.avi'):
fullname = filesdir + filename
# Create instane of VLC and create reference to movie.
vlcInstance = vlc.Instance("--aout=adummy")
media = vlcInstance.media_new(fullname)
# Create new instance of vlc player
player = vlcInstance.media_player_new()
# Load movie into vlc player instance
player.set_media(media)
# Start movie playback
player.play()
# Do not continue if video not finished
while player.get_state() != vlc.State.Ended:
# Quit if keyboard pressed during video
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
pygame.display.quit()
pygame.quit()
sys.exit()
player.stop()
# Quit if keyboard pressed during video
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
pygame.display.quit()
pygame.quit()
sys.exit()
I'm open to any alternative able to work with pictures AND videos.
EDIT: It was finally the time it takes to pygame to resize the (next) image with pygame.transform.scale().
Is there any way to optimise that ? Like for example, to print fullscreen without resizing the large images ?

I cannot reproduce the behaviour without the images and the videos, but here a couple of advices which should help in speed up the code when displaying images.
Do not use time.sleep(). It will freeze the game for the given time, so all calculations are done outside this time window, consuming more time. Better to use pygame time Clock. From the docs of its tick() method:
If you pass the optional framerate argument the function will delay to keep the game running slower than the given ticks per second. This can be used to help limit the runtime speed of a game. By calling Clock.tick(40) once per frame, the program will never run at more than 40 frames per second.
The tick() method should be called once per iteration in the main loop, so better to not put it inside an if statement.
Here:
screen.fill(black)
screen.blit(img, imgrect)
The first line screen.fill(black) is completely useless: you are redrawing the whole surface in the second line covering all the black background, since the image is rescaled to the screen size. You can safely blit the image without filling the background with black.
This will save time, because each time you use blit or fill, pygame in background does a lot of operation on the Surface to change the color of the pixels (the more the pixels changed, the longer the time needed).
This of course if any of the images you load has an alpha channel. If you have pictures with alpha channel, you need to paint black the background before. To save time, I suggest to remove the alpha channel from the images using another program.
pygame.transform.scale() requires time, especially if you have very large picture. Try to rescale your image with another program and load in pygame images of size the closer possible to your screen.
When loading the images, add .convert(). This will make blitting faster. Should be: img = pygame.image.load(fullname).convert().
In the end, your code should look like:
imgexts = ['png', 'jpg', 'jpeg']
videxts = ['mp4', 'mkv']
#filtering out non video and non image files in the directory using regex
#remember to import re module
showlist = [filename for filename in os.listdir(filesdir) if re.search('[' + '|'.join(imgexts + videxts) + ']$', filename.lower())]
clock = pygame.time.Clock()
while True:
# For every file in filesdir :
for filename in showlist:
filenamelower = filename.lower()
# If image:
if filenamelower.endswith('.png') or filenamelower.endswith('.jpg') or filenamelower.endswith('.jpeg'):
#all your stuff but NOT the time.sleep()
elif filenamelower.endswith('.mp4') or filenamelower.endswith('.mkv') or filenamelower.endswith('.avi'):
#unchanged here
clock.tick(0.25) #framerate = 0.25 means 1 frame each 4 seconds
for event in pygame.event.get():
#unchanged here

I figured out what were the issues, with the help of Valentino.
He helped me to optimize the code to improve the loading times of every image, that fixed the first issue.
See his answer.
Additionnally, I added a block of code :
# If image is not same dimensions
if imgrect.size != size:
img = Image.open(fullname)
img = img.resize(size, Image.ANTIALIAS)
img.save(fullname, optimize=True, quality=95)
img = pygame.image.load(fullname).convert()
imgrect = img.get_rect()
If the picture is not the screen resolution, I use Pillow (PIL) to resize and reduce the color palette to 8-bit (256 colors).
It reduces file sizes significantly (especially for big files) and allow pygame to load the image faster.
It fixed the second issue.
For those interested, the full code is :
import pygame
import sys
import vlc
import os
import re
from PIL import Image
filesdir = '/home/pi/SMBmount/'
imgexts = ['png', 'jpg', 'jpeg']
videxts = ['mp4', 'mkv', 'avi']
time = 5 # Time to display every img
#filtering out non video and non image files in the directory using regex
showlist = [filename for filename in os.listdir(filesdir) if re.search('[' + '|'.join(imgexts + videxts) + ']$', filename.lower())]
pygame.init()
size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()
while True:
# For every file in filesdir :
for filename in showlist:
filenamelower = filename.lower()
# If image:
if filenamelower.endswith('.png') or filenamelower.endswith('.jpg') or filenamelower.endswith('.jpeg'):
fullname = filesdir + filename
img = pygame.image.load(fullname).convert()
imgrect = img.get_rect()
# If image is not same dimensions
if imgrect.size != size:
img = Image.open(fullname)
img = img.resize(size, Image.ANTIALIAS)
img.save(fullname, optimize=True, quality=95)
img = pygame.image.load(fullname).convert()
imgrect = img.get_rect()
screen.blit(img, imgrect)
pygame.mouse.set_visible(False)
pygame.display.flip()
# Elif video:
elif filenamelower.endswith('.mp4') or filenamelower.endswith('.mkv') or filenamelower.endswith('.avi'):
fullname = filesdir + filename
# Create instane of VLC and create reference to movie.
vlcInstance = vlc.Instance("--aout=adummy")
media = vlcInstance.media_new(fullname)
# Create new instance of vlc player
player = vlcInstance.media_player_new()
# Load movie into vlc player instance
player.set_media(media)
# Start movie playback
player.play()
# Do not continue if video not finished
while player.get_state() != vlc.State.Ended:
# Quit if keyboard pressed during video
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
pygame.display.quit()
pygame.quit()
sys.exit()
player.stop()
clock.tick(1 / time) # framerate = 0.25 means 1 frame each 4 seconds
# Quit if keyboard pressed during video
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
pygame.display.quit()
pygame.quit()
sys.exit()

Related

How do I make a sprite as a gif in pygame? [duplicate]

This question already has answers here:
How can I load an animated GIF and get all of the individual frames in Pygame?
(3 answers)
Closed 2 years ago.
So I have this idea for a game where you dodge projectiles while controlling a helicopter. I am wondering if you can make a sprite appear as a gif, or something along the lines of two images switching every fraction of a second. I know how to make a sprite appear as one image:
self.surf = pygame.image.load("example.png").convert()
But I was wondering if this had an effect:
self.surf = pygame.image.load("example.gif").convert()
Unfortunately, it only displayed the first image in the gif.
Here is the gif:
Edit: Ok so I looked at the answers and tried to implement them in my code, but then it was all too confusing and I had tried to do something a bit more simple. This is what I came up with:
if play == 1:
self.surf = pygame.image.load("Image2.png").convert()
pygame.display.update()
play = 2
time.sleep(.2)
if play == 2:
self.surf = pygame.image.load("Image1.png").convert()
pygame.display.update()
play = 1
time.sleep(.2)
But, all that did was display the player sprite as image 1. Is there anything I can add to make this work?
PyGame respectively the pygame.image module can only handle non-animated GIFs.
But on the PyGame homepage is introduced the GIFImage library:
This library adds GIF animation playback to pygame.
Another option is to use the Pillow library (pip install Pillow).
Write a function, that can convert a PIL image to a pygame.Surface:
(see also PIL and pygame.image)
def pilImageToSurface(pilImage):
mode, size, data = pilImage.mode, pilImage.size, pilImage.tobytes()
return pygame.image.fromstring(data, size, mode).convert_alpha()
Use the PIL library to load a GIF frame by frame:
(see also Extracting The Frames Of An Animated GIF Using Pillow
def loadGIF(filename):
pilImage = Image.open(filename)
frames = []
if pilImage.format == 'GIF' and pilImage.is_animated:
for frame in ImageSequence.Iterator(pilImage):
pygameImage = pilImageToSurface(frame.convert('RGBA'))
frames.append(pygameImage)
else:
frames.append(pilImageToSurface(pilImage))
return frames
See also Load animated GIF and a simple animated gif viewer example:
import pygame
from PIL import Image
def pilImageToSurface(pilImage):
mode, size, data = pilImage.mode, pilImage.size, pilImage.tobytes()
return pygame.image.fromstring(data, size, mode).convert_alpha()
def loadGIF(filename):
pilImage = Image.open(filename)
frames = []
if pilImage.format == 'GIF' and pilImage.is_animated:
for frame in ImageSequence.Iterator(pilImage):
pygameImage = pilImageToSurface(frame.convert('RGBA'))
frames.append(pygameImage)
else:
frames.append(pilImageToSurface(pilImage))
return frames
pygame.init()
window = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
gifFrameList = loadGIF("my_gif.gif")
currentFrame = 0
run = True
while run:
clock.tick(20)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
rect = gifFrameList[currentFrame].get_rect(center = (250, 250))
window.blit(gifFrameList[currentFrame], rect)
currentFrame = (currentFrame + 1) % len(gifFrameList)
pygame.display.flip()
You can use the list of pygame.Surface objects to generate a Spritesheet.
Pygame can't do gifs, but if you really want to, you could animate it frame by frame one image at a time.

Placing an image to webcam feed using python - pygame in windows

i need to put a university uniform to a camera feed to place their faces on top of it and take a snapshot and upload it to our database. i need some help in my code written below.
1st problem: I cant call my webcam
2nd problem: my overlaying image isnt appearing right as i want it to be.(i need it to the bottom of the window...)
I am following this youtube tutorial.
import pygame, sys
import pygame.camera
from pygame.locals import *
pygame.init()
pygame.camera.init()
screen = pygame.display.set_mode((640, 480))
cam = pygame.camera.Camera("/dev/video0", (640,480))
cam.start()
background=pygame.Surface((window.get_rect().width,
window.get_rect().height))
background.fill((0, 0, 0))
image=pygame.image.load('College Boy Medium.png')
image=image.convert()
image = pygame.transform.scale(image, (720,640))
rect=image.get_rect()
while 1:
image = cam.get_image()
screen.blit(image,(0,0))
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit
Screen.blit(image,(0,0)) the 0,0 refers to the upper left of the image box x y position.
So 0,20 would place the image at x position 0 and the y position down 20 pixels.

Pygame Error: Unsupported Image Format

Hi I'm trying to follow these instructions from my last question:
Pygame: how to load 150 images and present each one for only .5sec per trial
This is the code I currently have and I'm unsure where I'm going wrong.
import pygame, glob
pygame.init()
pygame.mixer.init()
clock=pygame.time.Clock()
#### Set up window
window=pygame.display.set_mode((0,0),pygame.FULLSCREEN)
centre=window.get_rect().center
pygame.mouse.set_visible(False)
#colours
black = (0, 0, 0)
white = (255, 255, 255)
types= '*.tif'
artfile_names= []
for files in types:
artfile_names.extend(glob.glob(files))
image_list = []
for artwork in artfile_names:
image_list.append(pygame.image.load(artwork).convert())
##Randomizing
index=0
current_image = image_list[index]
if image_list[index]>= 150:
index=0
stimulus= pygame.image.load('image_list')
soundPlay=True
def artwork():
window.blit(stimulus,pygame.FULLSCREEN)
while not soundPlay:
for imageEvent in pygame.event.get():
artwork()
pygame.display.update()
clock.tick()
The first mistake is the same as in your previous question: types= '*.tif'. That means types is a string, but you actually wanted a tuple with the allowed file types: types = '*.tif', (the comma turns it into a tuple). So you iterate over the letters in '*.tif' and pass them to glob.glob which gives you all files in the directory and of course image_list.append(pygame.image.load(artwork).convert()) can't work if you pass it for example a .py file.
The next mistake is the stimulus = pygame.image.load('image_list') line which doesn't work because you need to pass a complete file name or path to the load function. I think your stimulus variable should actually be the current_image.
Here's a complete example that also shows you how to implement a timer.
import glob
import random
import pygame
pygame.init()
clock = pygame.time.Clock()
window = pygame.display.set_mode((640, 480))
file_types = '*.tif', # The comma turns it into a tuple.
# file_types = ['*.tif'] # Or use a list.
artfile_names = []
for file_type in file_types:
artfile_names.extend(glob.glob(file_type))
image_list = []
for artwork in artfile_names:
image_list.append(pygame.image.load(artwork).convert())
random.shuffle(image_list)
index = 0
current_image = image_list[index]
previous_time = pygame.time.get_ticks()
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# Calcualte the passed time, then increment the index, use
# modulo to keep it in the correct range and finally change
# the image.
current_time = pygame.time.get_ticks()
if current_time - previous_time > 500: # milliseconds
index += 1
index %= len(image_list)
current_image = image_list[index]
previous_time = current_time
# Draw everything.
window.fill((30, 30, 30)) # Clear the screen.
# Blit the current image.
window.blit(current_image, (100, 100))
pygame.display.update()
clock.tick(30)
Near the bottom you have the line:
stimulus= pygame.image.load('image_list')
Here you are trying to load an image titled image_list. There's no file extension there, so your OS doesn't recognize the filetype. But even if you did include a file extension, I think you're trying to load the individual images in the list image_list, and that's a whole other story.

Show and flip image using python

I am trying to encode a String to a QR code. Then show the QR code image on a display using python.
Here is my code:
import pyqrcode
from PIL import Image
import os
import pygame
from time import sleep
qr = pyqrcode.create("This is a string one")
qr.png("QR.png", scale=16)
pygame.init()
WIDTH = 1280
HEIGHT = 1080
scr = pygame.display.set_mode((WIDTH,HEIGHT),0,32)
img = pygame.image.load("QR.png")
scr.blit(img,(0,0))
pygame.display.flip()
sleep(3)
Now I want to display and flip the image in a loop.
I want to do it in a loop as the string ("This is a string one") is not constant. It will be updated (such as, I get string from mysql). When the string updates, I want to display the new QR code image after 3 seconds then flip it, then continue.
But when I put the code in a loop, it crashes and the image does not flip or update.
import pyqrcode
from PIL import Image
import os
import pygame
from time import sleep
while(1):
qr = pyqrcode.create("Nguyen Tran Thanh Lam")
qr.png("QR.png", scale=16)
pygame.init()
WIDTH = 1280
HEIGHT = 1080
scr = pygame.display.set_mode((WIDTH,HEIGHT),0,32)
img = pygame.image.load("QR.png")
scr.blit(img,(0,0))
pygame.display.flip()
sleep(5)
Update:
After 5 second, pygame-windows not flip. I must use Ctrl-C to Interrupt.
Traceback (most recent call last):
File "qr.py", line 18, in <module>
sleep(5)
KeyboardInterrupt
Thank you in advance.
pygame.display.flip doesn't flip the image, it updates the display/screen. To actually flip an image you have to use pygame.transform.flip.
There are various other problems, for example you should do the initialization, call pygame.display.set_mode and load the image before the while loop starts. After loading an image, call the convert or convert_alpha method to improve the blit performance:
img = pygame.image.load("QR.png").convert()
You also need to call pygame.event.pump() or use an event loop for event in pg.event.get():, otherwise the program will freeze because the OS thinks that your program has stopped responding.
To implement a timer you can use pygame.time.get_ticks. time.sleep makes your program unresponsive and should usually not be used in a game.
Here's an example:
import pygame as pg
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock() # A clock to limit the frame rate.
image = pg.Surface((100, 100))
image.fill((50, 90, 150))
pg.draw.rect(image, (120, 250, 70), (20, 20, 20, 20))
previous_flip_time = pg.time.get_ticks()
done = False
while not done:
for event in pg.event.get():
# Close the window if the users clicks the close button.
if event.type == pg.QUIT:
done = True
current_time = pg.time.get_ticks()
if current_time - previous_flip_time > 1000: # 1000 milliseconds.
# Flip horizontally.
image = pg.transform.flip(image, True, False)
previous_flip_time = current_time
screen.fill((30, 30, 30))
screen.blit(image, (100, 200))
# Refresh the display/screen.
pg.display.flip()
clock.tick(30) # Limit frame rate to 30 fps.
if __name__ == '__main__':
pg.init()
main()
pg.quit()

How to capture pygame screen?

How can I capture and save a sequence of images or a video of a pygame screen?
Basically I want to share my game video on youtube. Also, want to make a tutorial.
The game is rendered mainly in a loop:
def main():
while True:
GetInput()
Move()
Shift()
Draw()
With the Draw() function doing all the blit() and stuff before doing the pygame.display.flip()
Use pygame.image.save on your screen surface:
window = pygame.display.set_mode(...)
...
pygame.image.save(window, "screenshot.jpeg")
Note that this will slow down your program tremendously. If it is time-based, you may wish to fake the framerate when doing a capture.
The accepted answer said you could save the current screen with the pygame.image.save method
This is a good idea, however you might not want to save the images on the hard drive while the game is running, also pointed out by the question. Instead, you should save the screens in the program and then process them after the game has stopped running.
Here is my code with comments, that only shows the general idea of how a screen recording might work. It uses opencv (with numpy) and pygame but you have to install ffmpeg to convert the images to a video (try ffmpg in the terminal to test). Don't let the program run too long, because the saving still takes quite a while and is about proportional to the recorded frames. For more efficiency, you could only record every second frame or so.
from contextlib import contextmanager
import os
import time
import cv2
import numpy as np
import pygame as pg
from pygame import surfarray
def pg_to_cv2(cvarray:np.ndarray)->np.ndarray:
cvarray = cvarray.swapaxes(0,1) #rotate
cvarray = cv2.cvtColor(cvarray, cv2.COLOR_RGB2BGR) #RGB to BGR
return cvarray
def timer_wrapper(func):
def inner(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
#print("Finished:" ,func.__name__ ,end-start)
return end - start
return inner
#contextmanager
def video_writer(*args,**kwargs):
video = cv2.VideoWriter(*args,**kwargs)
try:
yield video
finally:
video.release()
#timer_wrapper
def save_frames(frames: list, average_dt: float|list, file_type: str = "mp4", name: str = "screen_recording"):
if type(average_dt) is list: average_dt = sum(average_dt)/len(average_dt) # force average_dt to be a float
size = frames[0].get_size()
codec_dict={
"avi":'DIVX',
"mp4":'MP4V'
}
codec = cv2.VideoWriter_fourcc(*codec_dict[file_type])
with video_writer(name+"."+file_type, codec, 1000/average_dt, size) as video: # file_name, codec, average_fps, dimensions
for frame in frames:
try:
pg_frame = surfarray.pixels3d(frame) # convert the surface to a np array. Only works with depth 24 or 32, not less
except:
pg_frame = surfarray.array3d(frame) # convert the surface to a np array. Works with any depth
cv_frame = pg_to_cv2(pg_frame) # then convert the np array so it is compatible with opencv
video.write(cv_frame) #write the frame to the video using opencv
def draw_fps(s:pg.Surface,clock:time.Clock):
fps = clock.get_fps()
sysfont.render_to(s,(100,100),str(fps),fgcolor=nice_green)
# initializing globals (colors, fonts, window, etc.)
pg.init()
sysfont = pg.freetype.SysFont(None,40)
BLACK = (0,)*3
nice_green = pg.Color("chartreuse2")
size=(1000, 600)
pg.display.set_caption("Screen Recording")
window = pg.display.set_mode(size)
# this is to save the frames
frames = []
dts = []
clock = pg.time.Clock()
running=True
try:
while running:
dt = clock.tick(60) # aim for ... fps
for event in pg.event.get():
if event.type == pg.QUIT:
running=False
if event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
running=False
window.fill(BLACK)
draw_fps(window,clock)
window_copy = window.copy() # if we don't copy the window then we will have the same image in all frames at the end
frames.append(window_copy) # We save the current frame together with the time passed between the frames
dts.append(dt)
pg.display.flip()
#if len(frames) >= 100: running = False # uncomment this to stop the game after ... frames for similar results in every run"
finally:
pg.quit()
# At this stage, the game ended and the cleanup process can start. For this we convert the frames to opencv images
# Only then we will write the video to the hard drive (That is what makes the save so slow).
# General information about the recording
frame_num = len(frames)
dt_sum = sum(dts)
average_dt = dt_sum/frame_num
# This is only an approximation:
# for each frame we have width * height many pixels -> frame_num * width * height
# A Surface needs get_bytesize() many bytes per pixel (In this case 4 bytes, because we set the depth of the display to 32 bits)
memory_usage_approx = frame_num * size[0] * size[1] * frames[0].get_bytesize() #https://www.pygame.org/docs/ref/surface.html#pygame.Surface.get_bytesize
print("Total time:" , dt_sum/1000,"s")
print("Average time per frame:" , average_dt,"ms")
print("Number of frames:", frame_num)
print("Memory usage approximation" , memory_usage_approx/1000, "KB")
args = (frames,dts,"avi","screen_recording")
time_for_save = save_frames(*args)
file_name = args[3]+"."+args[2]
video_memory_usage = os.path.getsize(file_name)
print("Video memory usage:" , video_memory_usage/1000, "KB")
with open("test.txt", "a") as f:
print("Total time:" , dt_sum/1000,"s\nNumber of frames:", frame_num,"\nSize:",size,"\nTime for save:",time_for_save,"s\nSaved in file:",file_name,file=f)
print("_"*100,file=f)
Or you just use a lib like Pygame Recorder.
x3 = pygame.surfarray.pixels3d(screen)
x3 = x3[:,:,::-1]
I found a cool way;
you can use
x3 = pygame.surfarray.pixels3d(window)
to get all the pixels on any surface or the screen (the window variable)!
You can use this in things like NumPy where you can use the code
array = numpy.uint8(x3)
to get the image of the surface as a NumPy array and then
im = PIL.Image.fromarray(array)
to make it a Pillow image, if you want. Then you can show it with a simple im.show() or just do whatever with it.

Categories