Newer
Older
#!/usr/bin/python3
import magic,os,pickle,re,subprocess
basedir = '/home/pi/kiosk'
contentbase = os.path.join(basedir, 'content')
slidebase = os.path.join(basedir, 'slides')
tmpdir = os.path.join(basedir, 'tmp')
statusfile = os.path.join(basedir, 'var', 'statusfile')
loopfile = os.path.join(basedir, 'var', 'loop')
looptmp = os.path.join(basedir, 'var', 'loop.tmp')
waitfile = os.path.join(basedir, 'var', 'wait')
waittmp = os.path.join(basedir, 'var', 'wait.tmp')
confcsv = os.path.join(basedir, 'var', 'conf.csv')
rclonebase = ['/usr/bin/rclone', 'sync']
synccommand = rclonebase + [remote, contentbase]
# Default parameter values; each value is given as a triple:
# [default, minimum, maximum]
defaults = {
'slide_rate' : [10, 2, 60],
'check_time' : [30, 1, 60],
}
# File formats that "feh" can display directly with no processing
directformats = [
'JPEG image',
'PNG image',
'GIF image',
'Netpbm image',
]
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# Video file formats that "omxplayer" can display
videoformats = [
'ISO Media',
'AVI',
'DivX',
]
# Template for loop script
looptemplate = '''#!/bin/bash
cd %s
feh --cycle-once -FZ -D %d
'''
# Template for wait script
waittemplate = '''#!/bin/bash
sleep %d
'''
# Keep track of history to avoid repeating work
filestatus = {}
# List of videos to play will get built here
videolist = []
# Actual parameter values will get filled in here
parameters = {}
# Set a parameter from the config file and the defaults
def setparam(parname):
# Set up parameters
v = -1
try:
v = int(parameters[parname])
except:
pass
if (v < defaults[parname][1]) or (v > defaults[parname][2]):
v = defaults[parname][0]
return(v)
# 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(filename):
srcpath = os.path.join(contentbase, 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
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, f)
subprocess.call(['/bin/mv', '-f', srcpath, dstpath])
filelist += [f]
else:
os.unlink(srcpath)
return(filelist)
# Write out a new version of an executable script file
def writescript(scriptpath, content):
tmppath = scriptpath + '.new'
fdout = open(tmppath, 'w')
fdout.write(content)
fdout.close()
subprocess.call(['/bin/chmod', 'u+x', tmppath])
subprocess.call(['/bin/mv', '-f', tmppath, scriptpath])
def processfile(f):
global videolist
print('processfile start', f)
filepath = os.path.join(contentbase, f)
sb = os.stat(filepath)
filetype = ms.file(filepath)
# Active file, save status record
keepcontent[f] = True
if f in filestatus:
# Status record exists
# Check size and modification time
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)
# See if it is video or slides
if thisstatus['v']:
# Mark this video to get played
videolist += [f]
else:
# mark resulting slide files to save them
for i in thisstatus['f']:
keepslides[i] = True
return
else:
# something has changed
print('changed', f)
filestatus[f]['s'] = sb.st_size
filestatus[f]['m'] = sb.st_mtime
filestatus[f]['t'] = filetype
filestatus[f]['v'] = False
else:
# new file, no status record yet, so make one
print('new', f)
filestatus[f] = {}
filestatus[f]['s'] = sb.st_size
filestatus[f]['m'] = sb.st_mtime
filestatus[f]['t'] = filetype
filestatus[f]['v'] = False
destpath = os.path.join(slidebase, f)
# Check for images that can be displayed directly as slides
for ff in directformats:
if ff in filetype:
# Check for files that can be played as video clips
video = False
for ff in videoformats:
if ff in filetype:
video = True
if direct:
# files that can be displayed directly just need to be copied into place
print('direct', filepath)
subprocess.call(['/bin/cp', '-p', '-f', filepath, destpath])
subprocess.call(['/home/pi/kiosk/bin/file-pdf'])
elif 'PostScript document' in filetype:
subprocess.call(['/home/pi/kiosk/bin/file-ps'])
elif 'PowerPoint' in filetype or 'Microsoft Word' in filetype or 'Zip archive data' in filetype:
subprocess.call(['/home/pi/kiosk/bin/file-office'])
elif 'ASCII text' in filetype:
subprocess.call(['/home/pi/kiosk/bin/file-text'])
else:
print('unknown', filepath)
print(filetype)
print(f, filetype, direct, video)
filelist = tmpmove(direct)
filestatus[f]['f'] = filelist
keepslides[i] = True
filestatus[f]['v'] = video
print('processfile done', f)
# end of functions, main program starts here
# Start by synchronizing with cloud storage to get content
# 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:
pass
# Slide files to preserve
keepcontent = {}
keepslides = {}
# 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 and record parameters
confdata = open(confcsv).readlines()
for confline in confdata[1:]:
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
fields = confline.split(',')
if len(fields) > 1:
parameter = fields[0].strip()
value = fields[1].strip()
parameters[parameter] = value
slide_rate = setparam('slide_rate')
check_time = setparam('check_time')
print('slide_rate', slide_rate)
print('check_time', check_time)
# Examine and process all content files
contentfiles = os.listdir(contentbase)
contentfiles.sort()
for f in contentfiles:
# Do not process config file as content
if f != 'conf.xlsx':
processfile(f)
# If requested, upload rendered slides for verification
try:
doupload = rclonebase + [slidebase, updst]
updst = os.path.join(remote, parameters['verify_folder'])
# subprocess.call(doupload)
except:
pass
# Prepare the looping script
loopscript = looptemplate % (slidebase, slide_rate)
# Videos get run one at a time
for video in videolist:
loopscript += "omxplayer -b '../content/%s'\n" % video
# Write out the completed looping script
writescript(loopfile, loopscript)
# Prepare and write out the waiting script (convert minutes to seconds)
waitscript = waittemplate % (check_time * 60)
writescript(waitfile, waitscript)
print('keepslides', keepslides)
# final cleanup, delete obsolete slides
# Get list of slide files
files = os.listdir(slidebase)
files.sort()
for f in files:
if f not in keepslides:
try:
os.unlink(os.path.join(slidebase, 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()