Python中的DIY数码相框

目录

介绍

背景

使用代码

配置文件

软件概述

软件功能

顶级程序代码

扫描要显示的照片

读取配置文件

主画面显示功能

准备要显示的图片


介绍

计算机显示屏显示照片库中的照片。这个用Python编写的软件应用程序几乎可以在任何运行的计算机python3cv2图像库上运行。

背景

数码照片显示应用有许多应用。然而,我有一个退休的老树莓派正在找工作,所以自然而然地,一个修补工程师应该花几周时间编写一些软件,这样灰胡子的老树莓派才能有一个有用的生活。我能说什么,有些人对他们的宠物多愁善感,我想Pi对我来说就像是某种宠物......

我决定使用Python环境,因为这允许我在Windows上开发,但在Linux上运行软件(Raspberian)。

使用代码

代码存储在单个Python源文件中,该文件使用python3解释器从命令行运行。

python3 digitalFotoFrame.py  photoframe.ini

有一个命令行参数——一些配置文件的名称,允许配置相框软件的操作。

配置文件

配置文件是一个简单的文本文件,包含以下四行键值格式。这些行可以按任何顺序排列。以#开头的注释行将被忽略。

DELAY=4.5
WAKE=6
SLEEP=22
PATH=/media/HDD 

有四个关键字(它们区分大小写,因此请全部使用大写字母!

DELAY=4.5

这意味着每张图片显示4.5秒。

WAKE=6

这将配置唤醒时间。此时,节目在本例中开始0600小时。

SLEEP=22

这是睡眠时间——此时,节目停止和屏幕空白——在这种情况下,2200小时或晚上10点。

PATH=/media/HDD

这是存储图片的文件夹——这里可以有包含图片的子文件夹。

WAKESLEEP小时之间,图片将以随机顺序连续显示。

SLEEPWAKE小时(夜间)之间,屏幕将空白,不显示任何图片。

所选PATH可能包含许多文件,分为文件夹和子文件夹。只有具有.jpg.png的文件才会被考虑显示。其他文件可能在此文件夹中,但它们将被忽略。

软件概述

该软件是为Python 3编写的单个Python源文件。它仅由三个Python函数和一个主程序组成。

在顶级视图中,应用程序将通过读取配置文件(在命令行上传入)进行初始化。从此文件中,提取了四个参数(如上所示)。

在操作级别,PATH将扫描要显示的图片。这些图片以随机顺序显示,在配置文件的DELAY时间内,每张图片都在屏幕上显示。这发生在一天中的WAKESLEEP小时之间。每小时一次——如果找到配置文件——参数将被重新读取(可能已更改),并重新扫描照片。

SLEEPWAKE小时之间,屏幕是空白的,不显示任何照片。

软件功能

该软件使用cv2库(opencv)进行图像功能,例如读取、调整大小、显示图片。有关此Python库的完整详细信息,请参阅下面的链接。它适用于WindowsMacOSLinuxRaspberian。此库必须安装在运行应用程序的计算机上。

pip install opencv-python

或:

pip3 install opencv-python 

链接到 OPENCV-PYTHON

Python库而言,其他依赖项都是标准依赖项,因此不需要特殊安装。

顶级程序代码

下面显示了应用程序的顶级Python代码。它基本上尝试查找和读取配置文件。这定义了photoFolderdelaywakehourbedtimehour的参数。如果无法成功读取配置文件,则应用程序将无法运行,并且将终止,并显示错误消息。

成功读取这些参数后,将调用应用程序的主要函数(runFotoFrame)来显示文件并处理唤醒和睡眠时间。

#------------------------------------------------------------
# top-level python script
#
# set up some global variables to default values.
# The config file sets these values on startup
#
# later on, these may be read and changed by a control
# file that is looked for every hour or two in the scanned
# folder.   If you want to change these while the pix frame
# is running, you can change this file at any time.
#
# command line:
#    python3 digitalFotoFrame.py configfile.txt
#-------------------------------------------------------------

if __name__ == "__main__":

    #----------------------------------    
    #---- default parameter values ----
    #----------------------------------
    photoFolder='/media/HDD'    # top level folder for pictures
    delay=4                     # seconds for each displayed picture
    wakehour=7                  # 07am = 0700 = 7, start showing pictures
    bedtimehour=21              # 10pm = 2100hrs = 21, stop showing pictures

    print("---- Digital Foto Frame - Starting ----")

    configFileRead=False
    configfn="photoframe.ini" # default name to look for hourly in top level folder
    # search arg list for
    if len(sys.argv)>1:
        configfn=sys.argv[1]

    print("reading config file: ",configfn)
    result=checkForControlFile(configfn,delay,wakehour,bedtimehour,photoFolder)
    if result[4]: # set to true if successful read of config file
        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) # wait just a bit to read messages if any
    if not configFileRead:
        print("\n--- Unable to read config file ---\n")
    else:
        # and then, let's get this show on the road
        params=[delay,wakehour,bedtimehour,photoFolder,configfn]
        runFotoFrame(params)  

扫描要显示的照片

scanForFiles函数用于递归扫描 PATH 文件夹和任何子文件夹以显示图片。它返回要显示的图片的文件名列表。我已经成功地将它用于超过10000个文件的结构。

#-------------------------------------------------------------
#--- scan a folder tree (recursively) for jpg or png files
#-------------------------------------------------------------
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(): # recurse for sub-folders
            x=scanForFiles(entry.path)
            pictureFiles.extend(x)
    #itr.close()
    return pictureFiles 

这里没什么特别的——Python os.scandir()标准函数用于扫描所有文件。扩展名为.jpg.png的文件将添加到列表中。任何文件夹都会递归扫描.jpg.png文件,这些文件也会添加到列表中。

读取配置文件

配置文件包含操作所需的设置。该checkForControlFile()函数用于尝试从外部文本文件中读取这些设置并返回值列表。

这些值以包含五个元素的列表的形式返回。

  • result[0]——延迟(以秒为单位)
  • result[1]——唤醒时间(小时)(0..23)
  • result[2]——睡眠时间(小时)(0..23)
  • result[3]——存储图片的路径
  • result[4]——true如果参数读取成功

#-------------------------------------------------------------
#--- Try to open a configuration file and read settings from it
#-------------------------------------------------------------
#-------------------------------------------------------------
def checkForControlFile(controlFn,delay,wakeHour,bedtimeHour,photoFolder):
    #
    #   Sample control file has four lines in KEY=VALUE format
    #   - delay in seconds
    #   - wake hour (0..23)
    #   - sleep hour (0..23)
    #   - path to find pictures and control file
    #   
    #   File is deposited into the top folder where the pictures are stored (PATH)
    #   File is named instructions.ini
    #   File has 4 lines
    #   
    #   Control file will be read hourly
    #   
    #DELAY=3.5
    #WAKE=6
    #SLEEP=21
    #PATH=/media/pi/photoframe
    #
    result=[delay,wakeHour,bedtimeHour,photoFolder,False]
    readparams=0 # bitwise log of keywords found to verify we had a full
    # configuration file with every line in it that we expect
    #print(controlFn)
    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) # limit 1..60
                    result[0]=x
                    readparams=readparams | 1
                if line.startswith('WAKE='):
                    x=float(line[5:])
                    x=max(0.,x)
                    x=min(10.,x) # limit 0..60
                    result[1]=int(x)
                    readparams=readparams | 2
                if line.startswith('SLEEP='):
                    x=float(line[6:])
                    x=max(0.,x)
                    x=min(23.,x) # limit 0..60
                    result[2]=int(x)
                    readparams=readparams | 4
                if line.startswith('PATH='):
                    result[3]=line[5:-1] # strip off new line at end
                    readparams=readparams | 8

    except:
        pass
    print('Read configuration file results ',result)
    if (readparams == 15):
        result[4] = True # read file properly, all 4 bits set = 1111 = 0xf = 15
    return result 

主画面显示功能

主要的图片显示功能是应用中比较复杂的功能。它处理两种模式(唤醒和显示照片、睡眠和空白屏幕)。它还处理这两种模式之间的转换。

它将定期(大约每小时一次)在指定路径中查找配置文件。如果找到一个,则读取新的配置文件,并扫描文件夹(甚至可能是新文件夹)以查找照片。这意味着,如果要更改照片,可以更改照片文件夹中的配置文件(必须命名为photoframe.ini)。这可以直接完成(通过一些文本编辑器),或者您可以将某些FTP可访问或SMB可访问共享驱动器上的照片文件夹复制到那里,然后在那里复制新图片和新的photoframe.ini文件)。

每小时一次,应用程序将查找photoframe.ini文件并重新扫描——因此您可以更新照片、更改照片文件夹、更改延迟时间、更改唤醒和睡眠时间,这些更改将在下一个小时的顶部得到响应。

准备要显示的图片

OpenCV库允许我们创建一个全屏窗口来显示图片。当然,这很好,但是显示器的尺寸(水平和垂直像素)可能与所显示图片的尺寸不匹配。例如,图片的水平尺寸可能比显示器宽,

为了确保所有图片在显示器上尽可能好地显示,我们可能需要调整它们的大小,使它们适合显示器的边界——这可能涉及根据图片放大或缩小。这是由函数底部的代码计算出来的。

调整图片大小后,其至少一个尺寸将与显示器的尺寸相同——因此它将垂直(侧面有一些额外的空间)或水平(顶部/底部有一些额外的空间)填充显示器。

现在,为了确保这些额外的空间显示为黑色像素,我们在侧面或顶部/底部添加了一些边框像素。这称为与图像接壤。在此边界步骤之后,我们将得到一张与显示器尺寸完全相同的图片,因此我们知道,在给定图片和屏幕尺寸的情况下,它以最佳方式显示。

#-------------------------------------------------------------
# main photo frame loop
#
# - Run the photo frame code continuously displaying photos
#   from the specified folder.   
# - Go dark at bedtimehour and light up again at wakehour
# - Check every hour for a new instructions.ini file with 
#   parameter updates.
# - Rescan for pictures every hour in case user has deleted 
#   or added pictures
# - One idea is that an FTP server or SAMBA remote disk mount
#   could be used to update the photos to be shown and to 
#   update the instructions.ini file to change parameters
#-------------------------------------------------------------
def runFotoFrame(params):

    # grab our parameters that control operation of the
    # digital foto frame
    delay=params[0]         # delay in seconds to show each picture
    wakehour=params[1]      # hour (int 24 hr format 0..23) to start showing
    bedtimehour=params[2]   # hour (in 24 hr format 0..23) to stop showing
    photoFolder=params[3]   # be real careful when changing this in config file
    configfn=params[4]      # name of config file to look for in top level folder 
    
    # determine if this is a windows OS based system
    isWindows=sys.platform.startswith('win') # 'win32' or 'linux2' or 'linux'

    # initialize a CV2 frame to cover the entire screen
    cv2frame='frame'
    cv2.namedWindow(cv2frame, cv2.WINDOW_NORMAL)
    cv2.setWindowProperty(cv2frame, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)

    # let's find out what size of display we're working with    
    tmp=cv2.getWindowImageRect(cv2frame)
    wid=float(tmp[2])
    hgt=float(tmp[3])
    # sometimes the getWindowImageRect returns nonsense like
    # width or height of -1 or some other non useful value
    if hgt<480. or wid<640.:
        hgt=1080. # assume a 9h x 16w form factor
        wid=1920.
    #print(hgt,wid)
    
    # scan the photoFolder for a list of picture files
    pictureFiles=scanForFiles(photoFolder)
    random.shuffle(pictureFiles) # randomly shuffle the picture files
    print(datetime.datetime.now(),'Scan found',len(pictureFiles),'files')

    # initialize for hourly and sleep processing    
    lastHour=datetime.datetime.now().hour
    sleeping=False
    done=False
    if not isWindows:
        os.system("xset s off") # screen blanking off
    
    # and loop forever (until some key is hit)
    while not done:
        
        # during waking hours, display pictures
        # during sleeping hours, keep display blanked
        for fn in pictureFiles:

            # let's see if it's time to do hourly tasks
            now=datetime.datetime.now()
            hour=now.hour
            if not isWindows:
                os.system("xset dpms force on");

            #-- hourly tasks, only done when not sleeping            
            if hour!=lastHour and not sleeping:
                lastHour=hour
                if not isWindows:
                    os.system("xset s off") # screen blanking off
                # try to read configuration file instructions.ini
                controlFn=os.path.join(photoFolder,configfn)
                result=checkForControlFile(controlFn,delay,wakehour,bedtimehour,photoFolder)
                if result[4]: # set to true for successful config file read
                    delay=result[0]
                    wakehour=result[1]
                    bedtimehour=result[2]
                    photoFolder=result[3]
                # rescan folder
                pictureFiles=scanForFiles(photoFolder)
                random.shuffle(pictureFiles)
                print(datetime.datetime.now(),'Scan found',len(pictureFiles),'files')

            #--- run always, do wake up tasks or sleep tasks
            #
            # for example wakehour might be 9am and bedtimehour might be 9pm
            # wakehour=9 and bedtimehour=21 (12+9)
            if hour>=wakehour and hour<bedtimehour:
                # we are in wake up time of day

                #--- if we were sleeping, then it is time to wake up
                if sleeping:
                    print(datetime.datetime.now(),'Wake up')
                    if not isWindows:
                        os.system("xset s off") # screen blanking off
                        os.system("xset dpms force on");
                    sleeping=False

                #--- display a photo
                # handle fault in loading a picture
                gotImg=False
                try:
                    print('loading',fn)
                    img = cv2.imread(fn, 1)
                    if len(img)>0:
                        gotImg=True
                except:
                    gotImg=False
                if not gotImg:
                    continue
    
                #-- now, maybe resize image so it shows up well without changing the aspect ratio
                #   add a border if the aspect ratio is different than the screen
                # so we upscale or downscale so it maxes out either the
                # horizontal or vertical portion of the screen
                # then add a border around it to make sure any left-over
                # parts of the screen are blacked out
                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]))
                #print(fn,img.shape,ratio,dims[1],dims[0])
                imgresized=cv2.resize(img,dims,interpolation = cv2.INTER_AREA)
                #print(imgresized.shape)
                # now, one dimension (width or height) will be same as screen dim
                # and the other may be smaller than the screen dim.
                # we're going to use cv.copyMakeBorder to add a border so we
                # end up with an image that is exactly screen sized
                widborder=max(1,int((wid-imgresized.shape[1])/2))
                hgtborder=max(1,int((hgt-imgresized.shape[0])/2))
                #print(hgtborder,widborder)
                imgbordered=cv2.copyMakeBorder(imgresized,hgtborder,hgtborder,widborder,widborder,cv2.BORDER_CONSTANT)
                #print('resized,bordered',imgbordered.shape)

                # and now show the image that has been resized and bordered
                cv2.imshow(cv2frame, imgbordered)
                #--- now we pause while the photo is displayed, we do this
                #    by waiting for a key stroke.
                k = cv2.waitKey(int(delay*1000)) & 0xff
                # 255 if no key pressed (-1) or ascii-key-code (13=CR, 27=esc, 65=A, 32=spacebar)
                if k!=0xff:
                    # if a key was pressed, exit the photo frame program
                    done=True
                    break  
            else:
                #-- during sleep time we go here
                # during sleep time, blank the screen
                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 # wait 300 seconds
                # 255 if no key pressed (-1) or ascii-key-code (13=CR, 27=esc, 65=A, 32=spacebar)
                if k!=0xff:
                    done=True
  
    # when the photo display session ends, 
    # we need to clean up the cv2 full-frame window
    cv2.destroyWindow(cv2frame) 

完整的源代码可在GitHub digitalFotoFrame上找到。

https://www.codeproject.com/Articles/5373372/A-DIY-Digital-Picture-Frame-in-Python

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值