diff --git a/ChangeLog b/ChangeLog index 3ddf613..0eb76f3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2009-05-01 Thomas Vander Stichele + + * examples/readtoc.py: + * morituri/program/cdrdao.py (added): + Move the ReadTocTask to the new cdrdao module. + 2009-05-01 Thomas Vander Stichele * examples/readtrack.py: diff --git a/examples/readtoc.py b/examples/readtoc.py index 4bcc47a..6e76c6f 100644 --- a/examples/readtoc.py +++ b/examples/readtoc.py @@ -1,192 +1,13 @@ # -*- 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 - +from morituri.program import cdrdao def main(): log.init() runner = task.SyncRunner() - t = ReadTOCTask() + t = cdrdao.ReadTOCTask() runner.run(t) print 'runner done', t.toc diff --git a/morituri/program/cdrdao.py b/morituri/program/cdrdao.py new file mode 100644 index 0000000..0fe9280 --- /dev/null +++ b/morituri/program/cdrdao.py @@ -0,0 +1,203 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 + +# Morituri - for those about to RIP + +# Copyright (C) 2009 Thomas Vander Stichele + +# This file is part of morituri. +# +# morituri is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# morituri is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with morituri. If not, see . + + +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