Skip to content
Snippets Groups Projects
run 7.82 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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()