A Python application reads pictures and displays them in a slide-show. Programmable wake and sleep times allow the display to be blanked during sleeping hours. The show can be modified from time to time to change features or pictures to be displayed.
Introduction
A computer display shows photos from a library of photos. This software application written in Python can run on almost any computer that runs python3
and the cv2
image library.
Background
There are numerous applications available for digital photo display applications. However, I had an old retired Raspberry Pi that was looking for a job, so naturally, a tinkering engineer should spend weeks writing some software so that the grey bearded old Raspberry Pi could have a useful life. What can I say, some people get sentimental about their pets, I guess the Pi felt like some kind of a pet for me...
I decided to use the Python environment as this allowed me to develop on Windows, yet run the software on Linux (Raspberian).
Using the Code
The code is stored in a single Python source file which is run from a command line using the python3
interpreter.
python3 digitalFotoFrame.py photoframe.ini
There is a single command line parameter - the name of some configuration file that allows the operation of the photo frame software to be configured.
Configuration File
The configuration file is a simple text file with the following four lines in key-value format. The lines can be in any order. Comment lines beginning with a #
are ignored.
DELAY=4.5
WAKE=6
SLEEP=22
PATH=/media/HDD
There are four keywords (they are case sensitive, so use all upper case!)
DELAY=4.5 | This means to show each picture for 4.5 seconds. |
WAKE=6 | This configures the wake-up time. At this time, the show starts in this case 0600 hours. |
SLEEP=22 | This is the sleep time - at this time, the show stops and screen blanks - in this case, 2200 hours or 10pm. |
PATH=/media/HDD | This is the folder where the pictures are stored - there can be subfolders here with pictures. |
Between the WAKE
and SLEEP
hours, pictures will be continuously displayed in a random order.
Between the SLEEP
and WAKE
hours (night time), the screen will be blanked and no pictures are shown.
The selected PATH
may contain many files, organized into folders and subfolders. Only files with .jpg or .png will be considered for display. Other files may be in this folder, but they will be ignored.
Software Overview
The software is a single Python source file written for Python 3. It consists of just three Python functions and a main program.
At a top level view, the application will initialize by reading the configuration file (passed in on the command line). From this file, four parameters (as shown above) are extracted.
At an operational level, the PATH
is scanned for pictures to display. These pictures are displayed in a random order with each being held on the screen for the DELAY
time from the configuration file. This happens between the WAKE
and SLEEP
hours of the day. Once per hour - if a configuration file is found - the parameters are re-read (possibly changed) and there is a new scan for photos.
Between the SLEEP
and WAKE
hours, the screen is blanked and no photos are displayed.
Software Functions
The software uses the cv2 library (opencv
) for image functions such as reading, resizing, displaying the pictures. See the link below for full details on this Python library. It is available for Windows, MacOS, Linux, and Raspberian. This library must be installed on the machine you are running the application on.
pip install opencv-python
or:
pip3 install opencv-python
link to OPENCV-PYTHON
The other dependencies as far as Python libraries are all standard ones, so no special installs are required.
Top Level Program Code
The following shows the top-level Python code for the application. It basically tries to find and read the configuration file. This defines the parameters for photoFolder
, delay
, wakehour
, and bedtimehour
. If the configuration file cannot be read successfully, the application cannot run and it will terminate, displaying an error message.
Once these parameters are read successfully, the main function of the application is called (runFotoFrame
) to display the files and handle wake and sleep times.
if __name__ == "__main__":
photoFolder='/media/HDD'
delay=4
wakehour=7
bedtimehour=21
print("---- Digital Foto Frame - Starting ----")
configFileRead=False
configfn="photoframe.ini"
if len(sys.argv)>1:
configfn=sys.argv[1]
print("reading config file: ",configfn)
result=checkForControlFile(configfn,delay,wakehour,bedtimehour,photoFolder)
if result[4]:
delay=result[0]
wakehour=result[1]
bedtimehour=result[2]
photoFolder=result[3]
configFileRead=True
print("Config file read: ",delay,wakehour,bedtimehour,photoFolder)
time.sleep(3)
if not configFileRead:
print("\n--- Unable to read config file ---\n")
else:
params=[delay,wakehour,bedtimehour,photoFolder,configfn]
runFotoFrame(params)
Scanning for Photos to Display
The function scanForFiles
is used to recursively scan the PATH folder and any sub-folders for pictures to display. It returns a list of file names of pictures to display. I've used it for a structure of over 10000 files successfully.
def scanForFiles(folder):
pictureFiles=[]
itr=os.scandir(folder)
for entry in itr:
if entry.is_file():
fn=entry.name.lower()
if fn.endswith('.jpg') or fn.endswith('.png'):
pictureFiles.append(entry.path)
if entry.is_dir():
x=scanForFiles(entry.path)
pictureFiles.extend(x)
return pictureFiles
Nothing special here - the Python standard os.scandir()
function is used to scan for all files. Files with .jpg or .png extensions are added to the list. Any folders are recursively scanned for .jpg or .png files and these are added to the list as well.
Reading the Configuration File
The configuration file contains settings needed for operation. The function checkForControlFile()
is used to try to read these settings from an external text file and return a list of the values.
The values are returned as a list of five elements.
result[0]
- the delay in seconds result[1]
- the wake time in hours (0..23) result[2]
- the sleep time in hours (0..23) result[3]
- the path where the pictures are stored result[4]
- true
if the parameters are read successfully
def checkForControlFile(controlFn,delay,wakeHour,bedtimeHour,photoFolder):
result=[delay,wakeHour,bedtimeHour,photoFolder,False]
readparams=0
try:
with open(controlFn,'r') as finp:
print(datetime.datetime.now(),'Reading configuration file')
for line in finp:
print(line)
if line.startswith('DELAY='):
x=float(line[6:])
x=max(1.,x)
x=min(60.,x)
result[0]=x
readparams=readparams | 1
if line.startswith('WAKE='):
x=float(line[5:])
x=max(0.,x)
x=min(10.,x)
result[1]=int(x)
readparams=readparams | 2
if line.startswith('SLEEP='):
x=float(line[6:])
x=max(0.,x)
x=min(23.,x)
result[2]=int(x)
readparams=readparams | 4
if line.startswith('PATH='):
result[3]=line[5:-1]
readparams=readparams | 8
except:
pass
print('Read configuration file results ',result)
if (readparams == 15):
result[4] = True
return result
Main Picture Display Function
The main picture display function is the more complex function of the application. It handles the two modes (awake and displaying photos, asleep and a blank screen). It also handles the transitions between these two modes.
Periodically (about once an hour), it will look in the specified path for a configuration file. If one is found, the new configuration file is read, and the folder (perhaps a new folder even) are scanned for photos. This means that if you want to change the photos, you can change the configuration file in the photos folder (which must be named photoframe.ini). This could be done directly (via some text editor) or you could potentially have the photos folder on some FTP accessible or SMB accessible shared drive and just copy new pictures and a new photoframe.ini file there).
Once per hour, the application will look for a photoframe.ini file and rescan - so you can update photos, change the photos folder, change the delay time, change the wake and sleep hours, and these changes will be responded to at the top of the next hour.
Preparing Pictures for Display
The OpenCV library allows us to create a 'full-screen' window to display the pictures in. Of course this is very nice, but the dimensions of the display (pixels horizontal and vertical) may not match the dimensions of the picture being displayed. The horizontal dimension of the picture might be wider than the display for example,
To make sure all the pictures display as well as possible on the display, we may need to resize them so that they fit within the bounds of the display - this may involve scaling up or down depending on the picture. This is figured out by the code towards the bottom of the function.
After resizing the picture, at least one of its dimensions will be the same as the dimension of the display - so it will fill the display either vertically (with some extra space on the sides) or horizontally (with some extra space at the top / bottom).
Now to make sure this extra space is displayed as black pixels, we add some border pixels to either the sides or the top/bottom. This is called bordering the image. After this bordering step, we have a picture that is exactly the same dimensions as the display so we know it is displayed in the best way possible given the dimensions of the picture and the screen.
def runFotoFrame(params):
delay=params[0]
wakehour=params[1]
bedtimehour=params[2]
photoFolder=params[3]
configfn=params[4]
isWindows=sys.platform.startswith('win')
cv2frame='frame'
cv2.namedWindow(cv2frame, cv2.WINDOW_NORMAL)
cv2.setWindowProperty(cv2frame, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
tmp=cv2.getWindowImageRect(cv2frame)
wid=float(tmp[2])
hgt=float(tmp[3])
if hgt<480. or wid<640.:
hgt=1080.
wid=1920.
pictureFiles=scanForFiles(photoFolder)
random.shuffle(pictureFiles)
print(datetime.datetime.now(),'Scan found',len(pictureFiles),'files')
lastHour=datetime.datetime.now().hour
sleeping=False
done=False
if not isWindows:
os.system("xset s off")
while not done:
for fn in pictureFiles:
now=datetime.datetime.now()
hour=now.hour
if not isWindows:
os.system("xset dpms force on");
if hour!=lastHour and not sleeping:
lastHour=hour
if not isWindows:
os.system("xset s off")
controlFn=os.path.join(photoFolder,configfn)
result=checkForControlFile(controlFn,delay,wakehour,bedtimehour,photoFolder)
if result[4]:
delay=result[0]
wakehour=result[1]
bedtimehour=result[2]
photoFolder=result[3]
pictureFiles=scanForFiles(photoFolder)
random.shuffle(pictureFiles)
print(datetime.datetime.now(),'Scan found',len(pictureFiles),'files')
if hour>=wakehour and hour<bedtimehour:
if sleeping:
print(datetime.datetime.now(),'Wake up')
if not isWindows:
os.system("xset s off")
os.system("xset dpms force on");
sleeping=False
gotImg=False
try:
print('loading',fn)
img = cv2.imread(fn, 1)
if len(img)>0:
gotImg=True
except:
gotImg=False
if not gotImg:
continue
widratio=wid/img.shape[1]
hgtratio=hgt/img.shape[0]
ratio=min(widratio,hgtratio)
dims=(int(ratio*img.shape[1]),int(ratio*img.shape[0]))
imgresized=cv2.resize(img,dims,interpolation = cv2.INTER_AREA)
widborder=max(1,int((wid-imgresized.shape[1])/2))
hgtborder=max(1,int((hgt-imgresized.shape[0])/2))
imgbordered=cv2.copyMakeBorder(imgresized,hgtborder,hgtborder,widborder,widborder,cv2.BORDER_CONSTANT)
cv2.imshow(cv2frame, imgbordered)
k = cv2.waitKey(int(delay*1000)) & 0xff
if k!=0xff:
done=True
break
else:
if not sleeping:
print(datetime.datetime.now(),'Going to sleep')
if not isWindows:
os.system("xset dpms force standby");
sleeping=True
k = cv2.waitKey(300*1000) & 0xff
if k!=0xff:
done=True
cv2.destroyWindow(cv2frame)
The complete source code is available on GitHub digitalFotoFrame.
History
- Version 1.0, November 27, 2023
- Version 1.1, November 28, 2023: Added discussion on preparing pictures for displaying on the physical screen
- Version 1.2, November 29, 2023: Changed language to Python