Class around PixMap objects that allows more python-like access. By Joe Strout.
[python/dscho.git] / Demo / sgi / video / VCR.py
blob535a7da3eb8d0edbc4feaa0a728f24555468b4a1
1 import fcntl
2 import IOCTL
3 from IOCTL import *
4 import sys
5 import struct
6 import select
7 import posix
8 import time
10 DEVICE='/dev/ttyd2'
12 class UnixFile:
13 def open(self, name, mode):
14 self.fd = posix.open(name, mode)
15 return self
17 def read(self, len):
18 return posix.read(self.fd, len)
20 def write(self, data):
21 dummy = posix.write(self.fd, data)
23 def flush(self):
24 pass
26 def fileno(self):
27 return self.fd
29 def close(self):
30 dummy = posix.close(self.fd)
32 def packttyargs(*args):
33 if type(args) <> type(()):
34 raise 'Incorrect argtype for packttyargs'
35 if type(args[0]) == type(1):
36 iflag, oflag, cflag, lflag, line, chars = args
37 elif type(args[0]) == type(()):
38 if len(args) <> 1:
39 raise 'Only 1 argument expected'
40 iflag, oflag, cflag, lflag, line, chars = args[0]
41 elif type(args[0]) == type([]):
42 if len(args) <> 1:
43 raise 'Only 1 argument expected'
44 [iflag, oflag, cflag, lflag, line, chars] = args[0]
45 str = struct.pack('hhhhb', iflag, oflag, cflag, lflag, line)
46 for c in chars:
47 str = str + c
48 return str
50 def nullttyargs():
51 chars = ['\0']*IOCTL.NCCS
52 return packttyargs(0, 0, 0, 0, 0, chars)
54 def unpackttyargs(str):
55 args = str[:-IOCTL.NCCS]
56 rawchars = str[-IOCTL.NCCS:]
57 chars = []
58 for c in rawchars:
59 chars.append(c)
60 iflag, oflag, cflag, lflag, line = struct.unpack('hhhhb', args)
61 return (iflag, oflag, cflag, lflag, line, chars)
63 def initline(name):
64 fp = UnixFile().open(name, 2)
65 ofp = fp
66 fd = fp.fileno()
67 rv = fcntl.ioctl(fd, IOCTL.TCGETA, nullttyargs())
68 iflag, oflag, cflag, lflag, line, chars = unpackttyargs(rv)
69 iflag = iflag & ~(INPCK|ISTRIP|INLCR|IUCLC|IXON|IXOFF)
70 oflag = oflag & ~OPOST
71 cflag = B9600|CS8|CREAD|CLOCAL
72 lflag = lflag & ~(ISIG|ICANON|ECHO|TOSTOP)
73 chars[VMIN] = chr(1)
74 chars[VTIME] = chr(0)
75 arg = packttyargs(iflag, oflag, cflag, lflag, line, chars)
76 dummy = fcntl.ioctl(fd, IOCTL.TCSETA, arg)
77 return fp, ofp
81 error = 'VCR.error'
83 # Commands/replies:
84 COMPLETION = '\x01'
85 ACK ='\x0a'
86 NAK ='\x0b'
88 NUMBER_N = 0x30
89 ENTER = '\x40'
91 EXP_7= '\xde'
92 EXP_8= '\xdf'
94 CL ='\x56'
95 CTRL_ENABLE = EXP_8 + '\xc6'
96 SEARCH_DATA = EXP_8 + '\x93'
97 ADDR_SENSE = '\x60'
99 PLAY ='\x3a'
100 STOP ='\x3f'
101 EJECT='\x2a'
102 FF ='\xab'
103 REW ='\xac'
104 STILL='\x4f'
105 STEP_FWD ='\x2b' # Was: '\xad'
106 FM_SELECT=EXP_8 + '\xc8'
107 FM_STILL=EXP_8 + '\xcd'
108 PREVIEW=EXP_7 + '\x9d'
109 REVIEW=EXP_7 + '\x9e'
110 DM_OFF=EXP_8 + '\xc9'
111 DM_SET=EXP_8 + '\xc4'
112 FWD_SHUTTLE='\xb5'
113 REV_SHUTTLE='\xb6'
114 EM_SELECT=EXP_8 + '\xc0'
115 N_FRAME_REC=EXP_8 + '\x92'
116 SEARCH_PREROLL=EXP_8 + '\x90'
117 EDIT_PB_STANDBY=EXP_8 + '\x96'
118 EDIT_PLAY=EXP_8 + '\x98'
119 AUTO_EDIT=EXP_7 + '\x9c'
121 IN_ENTRY=EXP_7 + '\x90'
122 IN_ENTRY_RESET=EXP_7 + '\x91'
123 IN_ENTRY_SET=EXP_7 + '\x98'
124 IN_ENTRY_INC=EXP_7 + '\x94'
125 IN_ENTRY_DEC=EXP_7 + '\x95'
126 IN_ENTRY_SENSE=EXP_7 + '\x9a'
128 OUT_ENTRY=EXP_7 + '\x92'
129 OUT_ENTRY_RESET=EXP_7 + '\x93'
130 OUT_ENTRY_SET=EXP_7 + '\x99'
131 OUT_ENTRY_INC=EXP_7 + '\x96'
132 OUT_ENTRY_DEC=EXP_7 + '\x98'
133 OUT_ENTRY_SENSE=EXP_7 + '\x9b'
135 MUTE_AUDIO = '\x24'
136 MUTE_AUDIO_OFF = '\x25'
137 MUTE_VIDEO = '\x26'
138 MUTE_VIDEO_OFF = '\x27'
139 MUTE_AV = EXP_7 + '\xc6'
140 MUTE_AV_OFF = EXP_7 + '\xc7'
142 DEBUG=0
144 class VCR:
145 def __init__(self):
146 self.ifp, self.ofp = initline(DEVICE)
147 self.busy_cmd = None
148 self.async = 0
149 self.cb = None
150 self.cb_arg = None
152 def _check(self):
153 if self.busy_cmd:
154 raise error, 'Another command active: '+self.busy_cmd
156 def _endlongcmd(self, name):
157 if not self.async:
158 self.waitready()
159 return 1
160 self.busy_cmd = name
161 return 'VCR BUSY'
163 def fileno(self):
164 return self.ifp.fileno()
166 def setasync(self, async):
167 self.async = async
169 def setcallback(self, cb, arg):
170 self.setasync(1)
171 self.cb = cb
172 self.cb_arg = arg
174 def poll(self):
175 if not self.async:
176 raise error, 'Can only call poll() in async mode'
177 if not self.busy_cmd:
178 return
179 if self.testready():
180 if self.cb:
181 apply(self.cb, (self.cb_arg,))
183 def _cmd(self, cmd):
184 if DEBUG:
185 print '>>>',`cmd`
186 self.ofp.write(cmd)
187 self.ofp.flush()
189 def _waitdata(self, len, tout):
190 rep = ''
191 while len > 0:
192 if tout == None:
193 ready, d1, d2 = select.select( \
194 [self.ifp], [], [])
195 else:
196 ready, d1, d2 = select.select( \
197 [self.ifp], [], [], tout)
198 if ready == []:
199 ## if rep:
200 ## print 'FLUSHED:', `rep`
201 return None
202 data = self.ifp.read(1)
203 if DEBUG:
204 print '<<<',`data`
205 if data == NAK:
206 return NAK
207 rep = rep + data
208 len = len - 1
209 return rep
211 def _reply(self, len):
212 data = self._waitdata(len, 10)
213 if data == None:
214 raise error, 'Lost contact with VCR'
215 return data
217 def _getnumber(self, len):
218 data = self._reply(len)
219 number = 0
220 for c in data:
221 digit = ord(c) - NUMBER_N
222 if digit < 0 or digit > 9:
223 raise error, 'Non-digit in number'+`c`
224 number = number*10 + digit
225 return number
227 def _iflush(self):
228 dummy = self._waitdata(10000, 0)
229 ## if dummy:
230 ## print 'IFLUSH:', dummy
232 def simplecmd(self,cmd):
233 self._iflush()
234 for ch in cmd:
235 self._cmd(ch)
236 rep = self._reply(1)
237 if rep == NAK:
238 return 0
239 elif rep <> ACK:
240 raise error, 'Unexpected reply:' + `rep`
241 return 1
243 def replycmd(self, cmd):
244 if not self.simplecmd(cmd[:-1]):
245 return 0
246 self._cmd(cmd[-1])
248 def _number(self, number, digits):
249 if number < 0:
250 raise error, 'Unexpected negative number:'+ `number`
251 maxnum = pow(10, digits)
252 if number > maxnum:
253 raise error, 'Number too big'
254 while maxnum > 1:
255 number = number % maxnum
256 maxnum = maxnum / 10
257 digit = number / maxnum
258 ok = self.simplecmd(chr(NUMBER_N + digit))
259 if not ok:
260 raise error, 'Error while transmitting number'
262 def initvcr(self, *optarg):
263 timeout = None
264 if optarg <> ():
265 timeout = optarg[0]
266 starttime = time.time()
267 self.busy_cmd = None
268 self._iflush()
269 while 1:
270 ## print 'SENDCL'
271 self._cmd(CL)
272 rep = self._waitdata(1, 2)
273 ## print `rep`
274 if rep in ( None, CL, NAK ):
275 if timeout:
276 if time.time() - starttime > timeout:
277 raise error, \
278 'No reply from VCR'
279 continue
280 break
281 if rep <> ACK:
282 raise error, 'Unexpected reply:' + `rep`
283 dummy = self.simplecmd(CTRL_ENABLE)
285 def waitready(self):
286 rep = self._waitdata(1, None)
287 if rep == None:
288 raise error, 'Unexpected None reply from waitdata'
289 if rep not in (COMPLETION, ACK):
290 self._iflush()
291 raise error, 'Unexpected waitready reply:' + `rep`
292 self.busy_cmd = None
294 def testready(self):
295 rep = self._waitdata(1, 0)
296 if rep == None:
297 return 0
298 if rep not in (COMPLETION, ACK):
299 self._iflush()
300 raise error, 'Unexpected waitready reply:' + `rep`
301 self.busy_cmd = None
302 return 1
304 def play(self): return self.simplecmd(PLAY)
305 def stop(self): return self.simplecmd(STOP)
306 def ff(self): return self.simplecmd(FF)
307 def rew(self): return self.simplecmd(REW)
308 def eject(self):return self.simplecmd(EJECT)
309 def still(self):return self.simplecmd(STILL)
310 def step(self): return self.simplecmd(STEP_FWD)
312 def goto(self, (h, m, s, f)):
313 if not self.simplecmd(SEARCH_DATA):
314 return 0
315 self._number(h, 2)
316 self._number(m, 2)
317 self._number(s, 2)
318 self._number(f, 2)
319 if not self.simplecmd(ENTER):
320 return 0
321 return self._endlongcmd('goto')
323 # XXXX TC_SENSE doesn't seem to work
324 def faulty_where(self):
325 self._check()
326 self._cmd(TC_SENSE)
327 h = self._getnumber(2)
328 m = self._getnumber(2)
329 s = self._getnumber(2)
330 f = self._getnumber(2)
331 return (h, m, s, f)
333 def where(self):
334 return self.addr2tc(self.sense())
336 def sense(self):
337 self._check()
338 self._cmd(ADDR_SENSE)
339 num = self._getnumber(5)
340 return num
342 def addr2tc(self, num):
343 f = num % 25
344 num = num / 25
345 s = num % 60
346 num = num / 60
347 m = num % 60
348 h = num / 60
349 return (h, m, s, f)
351 def tc2addr(self, (h, m, s, f)):
352 return ((h*60 + m)*60 + s)*25 + f
354 def fmmode(self, mode):
355 self._check()
356 if mode == 'off':
357 arg = 0
358 elif mode == 'buffer':
359 arg = 1
360 elif mode == 'dnr':
361 arg = 2
362 else:
363 raise error, 'fmmode arg should be off, buffer or dnr'
364 if not self.simplecmd(FM_SELECT):
365 return 0
366 self._number(arg, 1)
367 if not self.simplecmd(ENTER):
368 return 0
369 return 1
371 def mute(self, mode, value):
372 self._check()
373 if mode == 'audio':
374 cmds = (MUTE_AUDIO_OFF, MUTE_AUDIO)
375 elif mode == 'video':
376 cmds = (MUTE_VIDEO_OFF, MUTE_VIDEO)
377 elif mode == 'av':
378 cmds = (MUTE_AV_OFF, MUTE_AV)
379 else:
380 raise error, 'mute type should be audio, video or av'
381 cmd = cmds[value]
382 return self.simplecmd(cmd)
384 def editmode(self, mode):
385 self._check()
386 if mode == 'off':
387 a0 = a1 = a2 = 0
388 elif mode == 'format':
389 a0 = 4
390 a1 = 7
391 a2 = 4
392 elif mode == 'asmbl':
393 a0 = 1
394 a1 = 7
395 a2 = 4
396 elif mode == 'insert-video':
397 a0 = 2
398 a1 = 4
399 a2 = 0
400 else:
401 raise 'editmode should be off,format,asmbl or insert-video'
402 if not self.simplecmd(EM_SELECT):
403 return 0
404 self._number(a0, 1)
405 self._number(a1, 1)
406 self._number(a2, 1)
407 if not self.simplecmd(ENTER):
408 return 0
409 return 1
411 def autoedit(self):
412 self._check()
413 return self._endlongcmd(AUTO_EDIT)
415 def nframerec(self, num):
416 if not self.simplecmd(N_FRAME_REC):
417 return 0
418 self._number(num, 4)
419 if not self.simplecmd(ENTER):
420 return 0
421 return self._endlongcmd('nframerec')
423 def fmstill(self):
424 if not self.simplecmd(FM_STILL):
425 return 0
426 return self._endlongcmd('fmstill')
428 def preview(self):
429 if not self.simplecmd(PREVIEW):
430 return 0
431 return self._endlongcmd('preview')
433 def review(self):
434 if not self.simplecmd(REVIEW):
435 return 0
436 return self._endlongcmd('review')
438 def search_preroll(self):
439 if not self.simplecmd(SEARCH_PREROLL):
440 return 0
441 return self._endlongcmd('search_preroll')
443 def edit_pb_standby(self):
444 if not self.simplecmd(EDIT_PB_STANDBY):
445 return 0
446 return self._endlongcmd('edit_pb_standby')
448 def edit_play(self):
449 if not self.simplecmd(EDIT_PLAY):
450 return 0
451 return self._endlongcmd('edit_play')
453 def dmcontrol(self, mode):
454 self._check()
455 if mode == 'off':
456 return self.simplecmd(DM_OFF)
457 if mode == 'multi freeze':
458 num = 1000
459 elif mode == 'zoom freeze':
460 num = 2000
461 elif mode == 'digital slow':
462 num = 3000
463 elif mode == 'freeze':
464 num = 4011
465 else:
466 raise error, 'unknown dmcontrol argument: ' + `mode`
467 if not self.simplecmd(DM_SET):
468 return 0
469 self._number(num, 4)
470 if not self.simplecmd(ENTER):
471 return 0
472 return 1
474 def fwdshuttle(self, num):
475 if not self.simplecmd(FWD_SHUTTLE):
476 return 0
477 self._number(num, 1)
478 return 1
480 def revshuttle(self, num):
481 if not self.simplecmd(REV_SHUTTLE):
482 return 0
483 self._number(num, 1)
484 return 1
486 def getentry(self, which):
487 self._check()
488 if which == 'in':
489 cmd = IN_ENTRY_SENSE
490 elif which == 'out':
491 cmd = OUT_ENTRY_SENSE
492 self.replycmd(cmd)
493 h = self._getnumber(2)
494 m = self._getnumber(2)
495 s = self._getnumber(2)
496 f = self._getnumber(2)
497 return (h, m, s, f)
499 def inentry(self, arg):
500 return self.ioentry(arg, (IN_ENTRY, IN_ENTRY_RESET, \
501 IN_ENTRY_SET, IN_ENTRY_INC, IN_ENTRY_DEC))
503 def outentry(self, arg):
504 return self.ioentry(arg, (OUT_ENTRY, OUT_ENTRY_RESET, \
505 OUT_ENTRY_SET, OUT_ENTRY_INC, OUT_ENTRY_DEC))
507 def ioentry(self, arg, (Load, Clear, Set, Inc, Dec)):
508 self._check()
509 if type(arg) == type(()):
510 h, m, s, f = arg
511 if not self.simplecmd(Set):
512 return 0
513 self._number(h,2)
514 self._number(m,2)
515 self._number(s,2)
516 self._number(f,2)
517 if not self.simplecmd(ENTER):
518 return 0
519 return 1
520 elif arg == 'reset':
521 cmd = Clear
522 elif arg == 'load':
523 cmd = Load
524 elif arg == '+':
525 cmd = Inc
526 elif arg == '-':
527 cmd = Dec
528 else:
529 raise error, 'Arg should be +,-,reset,load or (h,m,s,f)'
530 return self.simplecmd(cmd)
532 def cancel(self):
533 d = self.simplecmd(CL)
534 self.busy_cmd = None