#!/usr/bin/env python

# bs2conv.py - compiles BML and BLM files into a BS2.BIN file as used by
# Blinkenstroem Advanced
# (see http://wiki.blinkenarea.org/bin/view/Blinkenarea/BlinkstroemAdvanced )
# 
# Usage:
#      python bs2conv.py [filenames]
#
# (C) 2005  Simon Budig <simon@gimp.org>, placed in the public domain.

import sys, re, xml.sax


# tweak this to adjust the overall speed of the movies.
blinkstroem_factor = 1.0 / 20


# The Handler for the XML/Sax parser needed for the BML movies.

class BMLHandler (xml.sax.handler.ContentHandler):
   def __init__ (self, filename, outfile):
      self.tagnesting = []
      self.filename = filename
      self.outfile = outfile
      self.bits = None
      self.width = None
      self.height = None
      self.duration = 100
      self.rowdata = ""

   # these functions do some dispatching for the different tags
   # via introspection.

   def startElement (self, name, attrs):
      self.tagnesting.append (name)
      handler = BMLHandler.__dict__.get ("handle_start_" + name, None)
      if handler: handler (self, attrs)

   def characters (self, chars):
      handler = BMLHandler.__dict__.get ("handle_chars_" + self.tagnesting[-1])
      if handler: handler (self, chars)

   def endElement (self, name):
      self.tagnesting.pop ()
      handler = BMLHandler.__dict__.get ("handle_end_" + name, None)
      if handler: handler (self)

   # actual handlers. Must have the name "handle_start_<tagname>"

   def handle_start_blm (self, attrs):
      print >>sys.stderr, filename + ": BML, %(width)sx%(height)s, %(bits)s bits" % attrs
      self.bits = int (attrs.get ("bits", 4))
      self.width = int (attrs.get ("width", 18))
      self.height = int (attrs.get ("height", 8))
      self.num_chars = (self.bits + 3) / 4
      self.regex = re.compile ("[0-9A-Fa-f]{%d}" % self.num_chars)


   def handle_start_frame (self, attrs):
      self.duration = int (attrs.get ("duration", 100))
      self.frame = []

   def handle_end_frame (self):
      assert len (self.frame) == self.width * self.height

      # We have row major pixels, need to transpose them to column major.
      transp_pixels = []
      for i in range (self.width):
         transp_pixels += (self.frame [i::self.width])

      # convert to 3 bit output
      if self.bits > 3:
         transp_pixels = [ i >> (self.bits - 3) for i in transp_pixels ]
      elif self.bits < 3:
         transp_pixels = [ i << (3 - self.bits) for i in transp_pixels ]

      ticks = int (round (self.duration * blinkstroem_factor))
      text = chr (ticks % 256) + chr (ticks / 256)
      text += "".join ([chr (i) for i in transp_pixels ])

      self.outfile.write (text)


   # The Sax parser might serve multiple CDATA parts for a single row
   def handle_start_row (self, attrs):
      self.rowdata = ""

   def handle_chars_row (self, chars):
      self.rowdata += chars

   def handle_end_row (self):
      pixels = [int (i, 16) for i in self.regex.findall (self.rowdata)]
      assert len (pixels) == self.width
      self.frame += pixels


# The function for parsing BLM files.

def parse_BLM_file (filename, outfile, width = 18, height = 8):
   print >>sys.stderr, filename + ": BLM, assuming %dx%d, 1 bit" % (width, height)
   f = file (filename)
   duration = 100
   frame = []

   line = f.readline ()
   while line:
      line = line.strip ()
      if line:
         if line[0] == "@":
            duration = int (line[1:])
            frame = []
         elif line[0] == "#":
            line = f.readline ()
            continue
         else:
            row = [[0, 7][int(i)] for i in list (line) if i in ['0', '1']]
            assert len (row) == width
            frame += row

         if len (frame) == width * height:
            # We have row major pixels, need to transpose them to column major.
            transp_pixels = []
            for i in range (width):
               transp_pixels += (frame [i::width])
            
            ticks = int (round (duration * blinkstroem_factor))
            text = chr (ticks % 256) + chr (ticks / 256)
            text += "".join ([chr (i) for i in transp_pixels ])

            outfile.write (text)

      line = f.readline ()



# Main routine when invoked from the commandline

if __name__=='__main__':
   if len (sys.argv) == 1:
      print >>sys.stderr, """Usage: %s [filenames]\n
Compiles the BLM and BML files given on the commandline into the "BS2.BIN"
File, as used by the BlinkenStroem Advanced""" % sys.argv[0]
      sys.exit (0)

   outfile = file ("BS2.BIN", "w")

   for filename in sys.argv[1:]:
      if filename[-4:].lower() == ".bml":
         pars = xml.sax.make_parser ()
         pars.setContentHandler (BMLHandler (filename, outfile))
         pars.parse (file (filename))
      elif filename[-4:].lower() == ".blm":
         parse_BLM_file (filename, outfile)
      else:
         print >>sys.stderr, filename + ": Unknown file type, ignoring..."



