# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 import re import os import subprocess import tempfile from morituri.common import task, log from morituri.image import toc from morituri.extern import asyncsub states = ['START', 'TRACK', 'LEADOUT', 'DONE'] _ANALYZING_RE = re.compile(r'^Analyzing track (?P\d+).*') _TRACK_RE = re.compile(r""" ^(?P[\d\s]{2})\s+ # Track \w+\s+ # Mode \d\s+ # Flags \d\d:\d\d:\d\d # Start in HH:MM:FF \((?P.+)\)\s+ # Start in frames \d\d:\d\d:\d\d # Length in HH:MM:FF \(.+\) # Length in frames """, re.VERBOSE) _LEADOUT_RE = re.compile(r""" ^Leadout\s \w+\s+ # Mode \d\s+ # Flags \d\d:\d\d:\d\d # Start in HH:MM:FF \((?P.+)\) # Start in frames """, re.VERBOSE) # FIXME: handle errors class ReadTOCTask(task.Task): """ I am a task that reads the TOC of a CD, including pregaps. """ description = "Reading TOC..." def __init__(self): self._buffer = "" # accumulate characters self._lines = [] # accumulate lines self._errors = [] # accumulate error lines self._lineIndex = 0 # where we are self._state = 'START' self._frames = None # number of frames self._starts = [] # start of each track, in frames self._track = None # which track are we analyzing? self._toc = None # path to temporary .toc file self.toc = None # result def start(self, runner): task.Task.start(self, runner) # FIXME: create a temporary file instead (fd, self._toc) = tempfile.mkstemp(suffix='.morituri') os.close(fd) os.unlink(self._toc) bufsize = 1024 self._popen = asyncsub.Popen(["cdrdao", "read-toc", self._toc], bufsize=bufsize, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) self.runner.schedule(1.0, self._read, runner) def _read(self, runner): ret = self._popen.recv_err() self.log(ret) if not ret: # could be finished now self.runner.schedule(1.0, self._poll, runner) return self._buffer += ret # find counter in LEADOUT state if self._buffer and self._state == 'LEADOUT': # split on lines that end in \r, which reset cursor to counter start # this misses the first one, but that's ok: # length 03:40:71...\n00:01:00 times = self._buffer.split('\r') position = "" while times and len(position) != 8: position = times.pop() # we need both a position reported and an Analyzing line # to have been parsed to report progress if position and self._track is not None: frame = self._starts[self._track - 1] or 0 \ + int(position[0:2]) * 60 * 75 \ + int(position[3:5]) * 75 \ + int(position[6:8]) self.setProgress(float(frame) / self._frames) # parse buffer into lines if possible, and parse them if "\n" in self._buffer: lines = self._buffer.split('\n') if lines[-1] != "\n": # last line didn't end yet self._buffer = lines[-1] del lines[-1] else: self._buffer = "" for line in lines: self.log('Parsing %s', line) if line.startswith('ERROR:'): self._errors.append(line) self._parse(lines) self._lines.extend(lines) self.runner.schedule(1.0, self._read, runner) def _poll(self, runner): if self._popen.poll() is None: self.runner.schedule(1.0, self._poll, runner) return self._done() def _done(self): self.setProgress(1.0) if self._popen.returncode != 0: if self._errors: print "\n".join(self._errors) else: print 'ERROR: exit code %r' % self._popen.returncode else: self.toc = toc.TOC(self._toc) self.toc.parse() os.unlink(self._toc) self.stop() return def _parse(self, lines): for line in lines: #print 'parsing', len(line), line methodName = "_parse_" + self._state getattr(self, methodName)(line) def _parse_START(self, line): if line.startswith('Track'): self.debug('Found possible track line') if line == "Track Mode Flags Start Length": self.debug('Found track line, moving to TRACK state') self._state = 'TRACK' def _parse_TRACK(self, line): if line.startswith('---'): return m = _TRACK_RE.search(line) if m: self._tracks = int(m.group('track')) self._starts.append(int(m.group('start'))) self.debug('Found track %d', self._tracks) m = _LEADOUT_RE.search(line) if m: self.debug('Found leadout line, moving to LEADOUT state') self._state = 'LEADOUT' self._frames = int(m.group('start')) self.debug('Found leadout at offset %r', self._frames) self.info('%d tracks found', self._tracks) return def _parse_LEADOUT(self, line): m = _ANALYZING_RE.search(line) if m: self.debug('Found analyzing line') track = int(m.group('track')) self.description = 'Analyzing track %d...' % track self._track = track #self.setProgress(float(track - 1) / self._tracks) #print 'analyzing', track def main(): log.init() runner = task.SyncRunner() t = ReadTOCTask() runner.run(t) print 'runner done', t.toc for track in t.toc.tracks: print track._indexes main()