#!/usr/bin/python3 import magic,os,pickle,re,subprocess basedir = '/home/pi/kiosk' remote = 'Kiosks:Kiosks/Test2' statusfile = os.path.join(basedir, 'state', 'statusfile') contentbase = os.path.join(basedir, 'content') slidebase = os.path.join(basedir, 'slides') tmpdir = os.path.join(basedir, 'tmp') loopfile = os.path.join(basedir, 'state', 'loop') looptmp = os.path.join(basedir, 'state', 'loop.tmp') confcsv = os.path.join(basedir, 'conf.csv') rclonebase = ['/usr/bin/rclone', 'sync'] synccommand = rclonebase + [remote, contentbase] # File formats that "feh" can display directly with no processing directformats = [ 'JPEG image', 'PNG image', 'GIF image', 'Netpbm image', ] # Interpreter line for the loop script loopscript = '#!/bin/bash\n' # Empty the temporary working directory def cleantmp(): try: tmpfiles = os.listdir(tmpdir) for f in tmpfiles: fpath = os.path.join(tmpdir, f) os.unlink(fpath) except: pass # Copy a file into the temporary directory def tmpcopy(contentdir, filename): srcpath = os.path.join(contentbase, contentdir, filename) dstpath = os.path.join(tmpdir, filename) subprocess.call(['/bin/cp', '-p', '-f', srcpath, dstpath]) # Move PNG files from temporary to slide directory # Returns a list of moved files, and cleans the temporary directory def tmpmove(contentdir, moveall): tmpfiles = os.listdir(tmpdir) tmpfiles.sort() filelist = [] for f in tmpfiles: srcpath = os.path.join(tmpdir, f) if moveall or (f[-4:] == '.png'): dstpath = os.path.join(slidebase, contentdir, f) subprocess.call(['/bin/mv', '-f', srcpath, dstpath]) filelist += [f] else: os.unlink(srcpath) return(filelist) # Synchronize with cloud storage subprocess.call(synccommand) # Set up to read magic numbers and guess file types ms = magic.open(magic.NONE) ms.load() # Empty temporary directory cleantmp() # Read previous file status, if it exists try: stf = open(statusfile, 'rb') filestatus = pickle.load(stf) stf.close() except: filestatus = {} # Slide files to preserve keepcontent = {} keepslides = {} def processfile(contentdir, f): print('processfile start', contentdir, f) slidedir = os.path.join(slidebase, contentdir) contentpath = os.path.join(contentbase, contentdir) filepath = os.path.join(contentpath, f) sb = os.stat(filepath) filetype = ms.file(filepath) filetag = os.path.join(contentdir, f) # Active file, save status record keepcontent[filetag] = True if filetag in filestatus: # Status record exists # Check size and modification time thisstatus = filestatus[filetag] if (thisstatus['s'] == sb.st_size) and (thisstatus['m'] == sb.st_mtime) and (thisstatus['t'] == filetype): # exact match, do not need to process this file print('exact', f) # mark resulting slide files to save them for i in thisstatus['f']: keepslides[os.path.join(contentdir, i)] = True return else: # something has changed print('changed', f) thisstatus['s'] = sb.st_size thisstatus['m'] = sb.st_mtime thisstatus['t'] = filetype else: # new file, no status record yet, so make one print('new', f) filestatus[filetag] = {} filestatus[filetag]['s'] = sb.st_size filestatus[filetag]['m'] = sb.st_mtime filestatus[filetag]['t'] = filetype # Figure out some useful paths tmppath = os.path.join(tmpdir, f) destpath = os.path.join(slidedir, f) direct = False for d in directformats: if d in filetype: direct = True if direct: # files that can be displayed directly just need to be copied into place print('direct', filepath) tmpcopy(contentdir, f) subprocess.call(['/bin/cp', '-p', '-f', filepath, destpath]) keepslides[filetag] = True elif 'PDF document' in filetype: tmpcopy(contentdir, f) subprocess.call(['/home/pi/kiosk/bin/file-pdf']) elif 'PostScript document' in filetype: tmpcopy(contentdir, f) subprocess.call(['/home/pi/kiosk/bin/file-ps']) elif 'PowerPoint' in filetype or 'Microsoft Word' in filetype or 'Zip archive data' in filetype: tmpcopy(contentdir, f) subprocess.call(['/home/pi/kiosk/bin/file-office']) elif 'ASCII text' in filetype: tmpcopy(contentdir, f) subprocess.call(['/home/pi/kiosk/bin/file-text']) else: print('unknown', filepath) print(filetype) filelist = tmpmove(contentdir, direct) filestatus[filetag]['f'] = filelist for i in filelist: keepslides[os.path.join(contentdir, i)] = True print('processfile done', contentdir, f) # Process all files in one directory into one set of slides def processdir(contentdir): slidedir = os.path.join(slidebase, contentdir) contentpath = os.path.join(contentbase, contentdir) # Make the slide directory, in case it is new try: os.mkdir(slidedir) except: pass # Get list of content files files = os.listdir(os.path.join(contentbase, contentdir)) files.sort() # Examine all content files for f in files: processfile(contentdir, f) contentdirs = ['.'] # Turn the configuration file from a Google Sheet to a CSV file subprocess.call(['/home/pi/kiosk/bin/process-conf']) # Run through the configuration file confdata = open(confcsv).readlines() for confline in confdata[1:]: # Ignore comments if confline[0] != '#': fields = confline.split(',') action = fields[0].strip().lower() parameter = fields[1].strip() pathname = fields[2].strip() if action == 'slides': loopscript += "cd '%s'\n" % pathname loopscript += "feh --cycle-once -FZ -D %s -R %s\n" % (parameter, parameter) loopscript += "cd ..\n" contentdirs += [pathname] processdir(pathname) uppath = fields[3].strip() if uppath != '-': upsrc = os.path.join(slidebase, pathname) updst = os.path.join(remote, uppath) doupload = rclonebase + [upsrc, updst] subprocess.call(doupload) if action == 'video': loopscript += "omxplayer -b '../content/%s'\n" % pathname loopout = open(looptmp, 'w') loopout.write(loopscript) loopout.close() subprocess.call(['/bin/chmod', 'u+x', looptmp]) subprocess.call(['/bin/mv', '-f', looptmp, loopfile]) print('keepslides', keepslides) # final cleanup, delete obsolete slides # Get list of slide files for d in contentdirs: slidedir = os.path.join(slidebase, d) files = os.listdir(slidedir) files.sort() for f in files: if os.path.join(d, f) not in keepslides: try: os.unlink(os.path.join(slidedir, f)) print('unlink slide', f) except: pass # Delete status records for obsolete content deletekeys = [] for s in filestatus.keys(): if s not in keepcontent: deletekeys += [s] for s in deletekeys: del filestatus[s] print('delete status', s) # Write new status file stf = open(statusfile, 'wb') pickle.dump(filestatus, stf) stf.close()