[CrackMonkey] STAR TACK

Nick Moffitt nick at zork.net
Sun Jan 20 13:08:28 PST 2002


[wrapper forwards out playing with THEDRAW]

---------- Forwarded message ----------
Date: Sun, 20 Jan 2002 03:38:01 -0500 (EST)
From: Kragen Sitaker <kragen at pobox.com>
To: kragen-hacks at canonical.org
Subject: record-movie, play-movie

I've often wanted something that can record byte streams with timing
intact and play them back with the timing intact.  So someone FoRKed a
site you could telnet to (towel.blinkenlights.nl, I think) to get
played an ASCII-art movie of Star Wars, and after watching a bit of
it, I knew I wanted a copy.

So I wrote these programs, and recorded a few sessions.  But,
unfortunately, the telnet site was unreliable --- it would close the
connection partway through the movie.  I looked around, found the
source of the movie, and translated it from the idiosyncratic format
it was in to the idiosyncratic format I was using.

And then I realized that a low-baud-rate simulator, useful for playing
back animated ASCII art from old BBSes, would be a cinch, so I wrote
that too.

All of this is available from http://pobox.com/~kragen/sw/movie.tar.gz.

So here's record-movie:
#!/usr/bin/python
# records a stream of bytes from stdin, inserting timing marks.
# Format is as follows:
# "%c%d:%s," where %c is a character, %d is an integer, %s is a string
# of length %d, is a record; note that this is a character plus one of
# Dan Bernstein's "netstrings".  Three types of records are defined:
#   when %c is 's', it's a string that was received.
#   when %c is 'e', it's an EOF, and the string is empty.
#   when %c is 't', it's a timestamp, and the string is a decimal number:
#     the number of seconds since start of recording.
#   when %c is 'c', it's a comment.
#   Playback programs are supposed to ignore records of unrecognized types.
# This program can't handle more than about nine megabytes per second on my
# laptop.

import sys, time, select, fcntl, FCNTL, math

def write_rec(code, content=''):
    assert len(code) == 1 and type(code) is type('s')
    sys.stdout.write("%s%d:%s," % (code, len(content), content))
    sys.stdout.flush()

def nplaces(number):
    """How many decimal places to keep for multiples of 'number'."""
    return max(0, math.ceil(-math.log(number)/math.log(10)))

def record_stream(precision=0.05):
    """Record a stream of bytes with timing information.

    precision specifies how precise to make the time recording: the
    minimum number of seconds to elapse between recorded timepoints,
    and also implicitly the number of digits of precision to record.

    """
    # This isn't an invasion of privacy, is it?  I don't think it is.
    write_rec('c', ("Movie made with Kragen Sitaker's record-movie v1.\n" +
    "See http://www.pobox.com/~kragen/sw/movie/ for more info.\n" +
    "This movie made at %(time)s\nwith %(version)s."
    ) % {'time': (time.asctime(time.localtime(time.time())) +
                  " %s/%s" % time.tzname),
         'version': "Python %s on a %s system" % (sys.version,
                                                  sys.platform)})
    infd = 0
    old_stdin_mode = fcntl.fcntl(infd, FCNTL.F_GETFL)
    timeformat = "%%.%df" % nplaces(precision)
    fcntl.fcntl(infd, FCNTL.F_SETFL, old_stdin_mode | FCNTL.O_NONBLOCK)
    start = time.time()
    last_recorded_time = start
    while 1:
        r, w, x = select.select([infd], [], [], None)
        if not infd in r: continue
        now = time.time()
        # we don't bother to record timepoints less than 'precision' apart
        if now > last_recorded_time + precision:
            reltime = now - start
            write_rec('t', timeformat % reltime)
            last_recorded_time = now
        try: string = sys.stdin.read(4096)
        except IOError, error:
            errnum, errmsg = error  # error, not error.args, for 1.5.2 compat
            if errnum == 0:  # this is a bug in Python
                string = ""
            else:    # XXX handle EAGAIN?
                raise
        if string == '':
            write_rec('e')
            fcntl.fcntl(infd, FCNTL.F_SETFL, old_stdin_mode)
            return
        else:
            write_rec('s', string)

if __name__ == "__main__": record_stream()

And here's play-movie, which takes an optional command-line argument
that tells it to play back its input at 24x normal speed:

#!/usr/local/bin/python
# play back a movie in the format of record-movie.

import sys, string, time

MovieError = "MovieError"

def read_rec():
    infile = sys.stdin
    type = infile.read(1)
    if type == '':
        return None # eof
    nums = []
    while 1:
        num = infile.read(1)
        if num == '': break
        if num in '0123456789': nums.append(num)
        else: break
    if num != ':':
        raise MovieError, ("invalid record lead %s" %
                           repr(string.join([type] + nums + [num], "")))
    length = int(string.join(nums, ""))
    contents = infile.read(length)
    if len(contents) != length:
        raise MovieError, "Record truncated: %s, %s < %s" % (type,
                                                             len(contents),
                                                             length)
    trailer = infile.read(1)
    if trailer != ',':
        raise MovieError, ("Invalid record termination '%s'" % trailer)
    return type, contents

def playback(argv):
    start = time.time()
    speed = 1
    if len(argv) > 1: speed = 24
    while 1:
        try: rec = read_rec()
        except MovieError, value:
            sys.stderr.write("Error in movie file: %s\n" % value)
            return
        if rec is None:
            sys.stderr.write("Premature EOF in movie\n")
            return
        type, content = rec
        if type == 's':
            sys.stdout.write(content)
            sys.stdout.flush()
        elif type == 't':
            now = time.time() - start
            target = float(content)/speed
            if target > now: time.sleep(target - now)
        elif type == 'e':
            return
        else:  # unknown record type
            pass

if __name__ == "__main__":
    try:
        playback(sys.argv)
    except KeyboardInterrupt:
        print "Interrupted"
        pass

Now, here's the low-baud-rate simulator:
#!/usr/bin/python
# Copies an input stream to output in the movie format play-movie expects,
# inserting timing marks every so many characters, so as to simulate a stream
# being delivered at so many baud with one start and one stop bit.

import sys

def writerec(type, str):
    sys.stdout.write("%s%d:%s," % (type, len(str), str))
    sys.stdout.flush()

def baud(baud, precision=0.01):
    cps = baud / 10.0
    newchars = max(int(cps * precision), 1)
    nchars = 0
    while 1:
        instr = sys.stdin.read(newchars)
	if len(instr) == 0:
	    writerec('e', '')
	    break
	nchars = nchars + len(instr)
        # XXX use nplaces from record-movie
    	writerec('t', "%.2f" % (nchars / cps))
        writerec('s', instr)

if __name__ == "__main__": baud(int(sys.argv[1]))

And here's the Star-Wars movie converter:
#!/usr/local/bin/python
# convert sw1.txt from swplay.jar, containing a star-wars movie, into
# a movie in play-movie's format

import string, time, sys

def write_rec(code, content=''):
    assert len(code) == 1 and type(code) is type('s')
    sys.stdout.write("%s%d:%s," % (code, len(content), content))
    sys.stdout.flush()

def convert():
    curtime = 0
    while 1:
        num = sys.stdin.readline()
        if num == '\xff\r\n':
            write_rec('e')
            return
        write_rec('s',
                  '\033[H\033[J' +   # clear screen
                  string.join([sys.stdin.readline() for i in range(13)], ''))
        # the number *before* the frame specifies how long to wait *after*
        # the frame before displaying the next one
        curtime = curtime + int(num) / 12.0
        write_rec('t', "%.2f" % curtime)


if __name__ == "__main__": convert()

The actual Star Wars ASCII art movie is available from several places,
but is 61K gzipped, which I think is too big to fit into this mail.



----- End forwarded message -----

-- 
INFORMATION GLADLY GIVEN BUT SAFETY REQUIRES AVOIDING UNNECESSARY CONVERSATION
 
	01234567 <- The amazing* indent-o-meter! 
        ^	    (*: Indent-o-meter may not actually amaze.)




More information about the Crackmonkey mailing list