Skip to content
Snippets Groups Projects
run 7.82 KiB
Newer Older
don's avatar
don committed
#!/usr/bin/python3
import magic,os,pickle,re,subprocess

basedir = '/home/pi/kiosk'
remote = 'Kiosks:Kiosks/Test3'
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')
waitfile    = os.path.join(basedir, 'var', 'wait')
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],
}
don's avatar
don committed

# File formats that "feh" can display directly with no processing
directformats = [
  'JPEG image',
  'PNG image',
  'GIF image',
  'Netpbm image',
]

# Video file formats that "omxplayer" can display
videoformats = [
  'ISO Media',
  'AVI',
  'DivX',
]

# Template for loop script
looptemplate = '''#!/bin/bash
cd %s
don's avatar
don committed
export DISPLAY=:0
don's avatar
don committed
echo feh-b $DISPLAY
don's avatar
don committed
pwd
don's avatar
don committed
echo feh --cycle-once -FZ -D %d
feh --cycle-once -FZ -D %d
don's avatar
don committed
pwd
don's avatar
don committed
echo feh-e $DISPLAY
'''

# 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)
don's avatar
don committed
# 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)
don's avatar
don committed
  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(moveall):
don's avatar
don committed
  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)
don's avatar
don committed
      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])
don's avatar
don committed

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
    thisstatus = filestatus[f]
    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

  # Figure out some useful paths
  destpath = os.path.join(slidebase, f)

  # Check for images that can be displayed directly as slides
  direct = False
  for ff in directformats:
    if ff in filetype:
       direct = True

  # 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])
    keepslides[f] = True
  elif 'PDF document' in filetype:
    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'])
  elif video:
    videolist += [f]
  else:
    print('unknown', filepath)
    print(filetype)
  print(f, filetype, direct, video)
  filelist = tmpmove(direct)
  filestatus[f]['f'] = filelist
  for i in 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()
don's avatar
don committed

# 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:]:
  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
don's avatar
don committed
loopscript = looptemplate % (slidebase, slide_rate, 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)
don's avatar
don committed
# 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
don's avatar
don committed

# 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()