[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