OverviewFilesCommitsIssuesDocsReleasesPeers

cthuloid / cuemgr / cueengine.py

186 lines · python ref:
"""Engine for navigating through cues loaded from a cuesheet.

Cues can be nested in scenes. This engine supports stepping forward and backward through cues,
and for jumping to other scenes. See cue.py for more info.

"""

import time, threading
from console import *
from cue import *


class Scene:
  def __init__(self, name):
    self.name = name
    self.cues = []
  def add(self, cue):
    self.cues.append(cue)


class CueEngine:
  def __init__(self):
    self.scenes = [Scene('Beginning Blackout')]
    #self.scenes[0].add(CueLoad('load off'))
    self.reset()

  def reset(self):
    self.ixScene = 0  # current scene
    self.ixCue = -1   # cue we just ran

  def thisScene(self): return self.scenes[self.ixScene]
  def thisCue(self):   return self.thisScene().cues[self.ixCue]

  def add(self, scene):
    self.scenes.append(scene)

  def getLoc(self):
    #return (self.ixScene, self.ixCue)
    return None

  def printLocStr(self, printThisCue=True):
    s ='(' + self.thisScene().name + ')'
    if printThisCue and len(self.thisScene().cues):
      s += ' - '
      if self.ixCue >= 0: s+= self.thisCue().line.strip()
      else: s += self.thisScene().cues[1 + self.ixCue].line.strip()
    print(s)

  def onLastCue(self):
    return 1+self.ixCue >= len(self.thisScene().cues)
  def onLastScene(self):
    return 1+self.ixScene >= len(self.scenes)
  def onFirstScene(self):
    return self.ixScene <= 0

  def nextCue(self):
    if self.onLastCue():
      if self.onLastScene():
        print('At End')
        return
      self.ixScene += 1
      self.ixCue = 0
      print('--------------------------------------------------------------')
      #print(self.getLoc(), 'Scene:', self.thisScene().name)
    else:
      self.ixCue += 1

    self.printLocStr()
    #print(self.getLoc(), 'next cue:', self.thisCue().line.strip())
    self.thisCue().run()

  def prevCue(self):
    if self.ixCue <= 0:
      if self.onFirstScene():
        print('At Beginning')
        return
      self.ixScene -= 1
      self.ixCue = len(self.thisScene().cues) - 1
      print('--------------------------------------------------------------')
      #print(self.getLoc(), 'Scene:', self.thisScene().name)
    else:
      self.ixCue -= 1

    self.printLocStr()
    #print(self.getLoc(), 'previous cue:', self.thisCue().line.strip())
    self.thisCue().run(True)

  def nextScene(self):
    if self.onLastScene():
      print('At End')
      return
    self.ixScene += 1
    self.ixCue = -1
    #print(self.getLoc(), 'next Scene:', self.thisScene().name)
    self.printLocStr(False)

  def prevScene(self):
    if self.onFirstScene():
      print('At Beginning')
      return
    self.ixScene -= 1
    self.ixCue = -1
    #print(self.getLoc(), 'previous Scene:', self.thisScene().name)
    self.printLocStr(False)

  def loadCueSheet(self, filename):
    try:  # parse the cues file
      with open(filename) as f:
        lineNum = 0
        curScene = None
        indentStack = []  # stack of tuples of (node, indent_len)

        def pushIndent(node, indentLen): indentStack.append((node, indentLen))
        def popIndent(): indentStack.pop()
        def topIndent(): return indentStack[-1]
        def topIndentLen(): return indentStack[-1][1]

        seenTab, seenSpace = False, False
        pushIndent(self, -1)

        line = f.readline()
        while line:
          lineNum += 1
          tokens = line.split()

          try:
            # skip empty lines and lines where the first token is #
            if len(tokens) and not tokens[0].startswith('#'):
              cmd = tokens[0]
              indent = line[:line.find(cmd)]
              indentLen = len(indent)
              thisNode = None

              #test for mixed indentation characters
              if indent.find(' ') >= 0: seenSpace = True
              if indent.find('\t') >= 0: seenTab = True
              if seenSpace and seenTab:
                raise TabError('The cue file mixes spaces and tabs.  Please use one or the other')

              # remove cmds with <= indent, because their block/revelance is over
              pops = 0
              while indentLen < topIndentLen():
                popIndent()
                pops += 1

              # did we pop to a previously unused indent? Handles the following:
              # s
              #   s
              #  s
              if pops > 0 and indentLen > topIndentLen():
                raise IndentationError('Indentation level does not match a previous block')

              if indentLen == topIndentLen():
                popIndent()

              if cmd == 'scene':
                thisNode = Scene(restAfterWord(cmd, line))
              elif cmd == 'seq':
                thisNode = CueSequence(line)
              elif cmd in CueClassMap:
                thisNode = CueClassMap[cmd](line)
              else:
                print('Error unrecognized command on line', lineNum)
                print('Text:', line)
                return

              if thisNode:
                topIndent()[0].add(thisNode)
                pushIndent(thisNode, indentLen)

          except BaseException as e:
            print('Error:', e)
            print('Line Number:', lineNum)
            print('Text:', line)
            return

          line = f.readline()
      self.reset()
    except BaseException as e:
      print('Error:', e)
      getchMsg()

if __name__ == '__main__':
  CueMgr = CueEngine()
  CueMgr.loadCueSheet('cuesheet.txt')