def read_data(cls, data): if (not data.startswith(u"# xmcd")): raise XMCDException("") disc_length = re.search(r'# Disc length: (\d+)', data) if (disc_length is not None): disc_length = int(disc_length.group(1)) track_lengths = re.compile(r'# Track frame offsets:\s+[#\s\d]+', re.DOTALL).search(data) if (track_lengths is not None): track_lengths = map(int, re.findall(r'\d+', track_lengths.group(0))) fields = {} for line in re.findall(r'.+=.*[\r\n]', data): (field, value) = line.split(u'=', 1) field = field.encode('ascii') value = value.rstrip('\r\n') if (field in fields.keys()): fields[field] += value else: fields[field] = value return XMCD(values=fields, offsets=track_lengths, length=disc_length)
def from_string(cls, string): # try: # data = string.decode('latin-1') # except UnicodeDecodeError: # data = string.decode('utf-8','replace') #FIXME - handle latin-1 files? data = string.decode('utf-8', 'replace') if (not data.startswith(u"# xmcd")): raise XMCDException() fields = {} comments = [] field_line = re.compile(r'([A-Z0-9]+?)=(.*)') for line in StringIO.StringIO(data): if (line.startswith(u'#')): comments.append(line.rstrip('\r\n')) else: match = field_line.match(line.rstrip('\r\n')) if (match is not None): key = match.group(1).encode('ascii') value = match.group(2) if (key in fields): fields[key] += value else: fields[key] = value return cls(fields, comments)
def __len__(self): track_field = re.compile(r"(TTITLE|EXTT)(\d+)") return ( max(set([int(m.group(2)) for m in [track_field.match(key) for key in self.fields.keys()] if m is not None])) + 1 )
def __len__(self): track_field = re.compile(r'(TTITLE|EXTT)(\d+)') return max( set([ int(m.group(2)) for m in [track_field.match(key) for key in self.fields.keys()] if m is not None ])) + 1
def stream(self): titleset = re.compile("ATS_%2.2d_\\d\\.AOB" % (self.titleset)) return AOBStream( aob_files=sorted([self.dvdaudio.files[key] for key in self.dvdaudio.files.keys() if (titleset.match(key))]), first_sector=self[0].first_sector, last_sector=self[-1].last_sector, unprotector=self.dvdaudio.unprotector)
class FreeDB: LINE = re.compile(r'\d\d\d\s.+') def __init__(self, server, port, messenger): self.server = server self.port = port self.socket = None self.r = None self.w = None self.messenger = messenger def connect(self): import socket try: self.messenger.info(_(u"Connecting to \"%s\"") % (self.server)) self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect((self.server, self.port)) self.r = self.socket.makefile("rb") self.w = self.socket.makefile("wb") (code, msg) = self.read() #the welcome message if (code == 201): self.messenger.info(_(u"Connected ... attempting to login")) else: self.r.close() self.w.close() self.socket.close() raise FreeDBException(_(u"Invalid hello message")) self.write("cddb hello user %s %s %s" % \ (socket.getfqdn(),"audiotools",VERSION)) (code, msg) = self.read() #the handshake successful message if (code != 200): self.r.close() self.w.close() self.socket.close() raise FreeDBException(_(u"Handshake unsuccessful")) self.write("proto 6") (code, msg) = self.read() #the protocol successful message if ((code != 200) and (code != 201)): self.r.close() self.w.close() self.socket.close() raise FreeDBException(_(u"Protocol change unsuccessful")) except socket.error, err: raise FreeDBException(err[1])
def __init__(self, audio_ts_path, cdrom_device=None): """A DVD-A which contains PCMReader-compatible track objects.""" #an inventory of AUDIO_TS files converted to uppercase keys self.files = dict([(name.upper(), os.path.join(audio_ts_path, name)) for name in os.listdir(audio_ts_path)]) titleset_numbers = list(self.__titlesets__()) #for each titleset, read an ATS_XX_0.IFO file #each titleset contains one or more DVDATitle objects #and each DVDATitle object contains one or more DVDATrack objects self.titlesets = [self.__titles__(titleset) for titleset in titleset_numbers] #for each titleset, calculate the lengths of the corresponding AOBs #in terms of 2048 byte sectors self.aob_sectors = [] for titleset in titleset_numbers: aob_re = re.compile("ATS_%2.2d_\\d\\.AOB" % (titleset)) titleset_aobs = dict([(key, value) for (key, value) in self.files.items() if (aob_re.match(key))]) for aob_length in [os.path.getsize(titleset_aobs[key]) / DVDAudio.SECTOR_SIZE for key in sorted(titleset_aobs.keys())]: if (len(self.aob_sectors) == 0): self.aob_sectors.append( (0, aob_length)) else: self.aob_sectors.append( (self.aob_sectors[-1][1], self.aob_sectors[-1][1] + aob_length)) try: if ((cdrom_device is not None) and ('DVDAUDIO.MKB' in self.files.keys())): from audiotools.prot import CPPMDecoder self.unprotector = CPPMDecoder( cdrom_device, self.files['DVDAUDIO.MKB']).decode else: self.unprotector = lambda sector: sector except ImportError: self.unprotector = lambda sector: sector
def info(self): """returns a (sample_rate, channels, channel_mask, bps, type) tuple""" #find the AOB file of the title's first track track_sector = self[0].first_sector titleset = re.compile("ATS_%2.2d_\\d\\.AOB" % (self.titleset)) for aob_path in sorted([self.dvdaudio.files[key] for key in self.dvdaudio.files.keys() if (titleset.match(key))]): aob_sectors = os.path.getsize(aob_path) / DVDAudio.SECTOR_SIZE if (track_sector > aob_sectors): track_sector -= aob_sectors else: break else: raise ValueError(_(u"unable to find track sector in AOB files")) #open that AOB file and seek to that track's first sector aob_file = open(aob_path, 'rb') try: aob_file.seek(track_sector * DVDAudio.SECTOR_SIZE) #read the pack header DVDAudio.PACK_HEADER.parse_stream(aob_file) #skip packets until the stream ID 0xBD is found pes_header = DVDAudio.PES_HEADER.parse_stream(aob_file) while (pes_header.stream_id != 0xBD): aob_file.read(pes_header.packet_length) pes_header = DVDAudio.PES_HEADER.parse_stream(aob_file) #parse the PCM/MLP header header = DVDAudio.PACKET_HEADER.parse_stream(aob_file) #return the values indicated by the header return (DVDATrack.SAMPLE_RATE[ header.info.group1_sample_rate], DVDATrack.CHANNELS[ header.info.channel_assignment], DVDATrack.CHANNEL_MASK[ header.info.channel_assignment], DVDATrack.BITS_PER_SAMPLE[ header.info.group1_bps], header.stream_id) finally: aob_file.close()
def __parse_info__(self): """generates a cache of sample_rate, bits-per-sample, etc.""" if (len(self.tracks) == 0): return #Why is this post-init processing necessary? #DVDATrack references DVDATitle #so a DVDATitle must exist when DVDATrack is initialized. #But because reading this info requires knowing the sector #of the first track, we wind up with a circular dependency. #Doing a "post-process" pass fixes that problem. #find the AOB file of the title's first track track_sector = self[0].first_sector titleset = re.compile("ATS_%2.2d_\\d\\.AOB" % (self.titleset)) for aob_path in sorted([self.dvdaudio.files[key] for key in self.dvdaudio.files.keys() if (titleset.match(key))]): aob_sectors = os.path.getsize(aob_path) / DVDAudio.SECTOR_SIZE if (track_sector > aob_sectors): track_sector -= aob_sectors else: break else: raise ValueError(_(u"unable to find track sector in AOB files")) #open that AOB file and seek to that track's first sector aob_file = open(aob_path, 'rb') try: aob_file.seek(track_sector * DVDAudio.SECTOR_SIZE) aob_reader = BitstreamReader(aob_file, 0) #read and validate the pack header #(there's one pack header per sector, at the sector's start) (sync_bytes, marker1, current_pts_high, marker2, current_pts_mid, marker3, current_pts_low, marker4, scr_extension, marker5, bit_rate, marker6, stuffing_length) = aob_reader.parse( "32u 2u 3u 1u 15u 1u 15u 1u 9u 1u 22u 2u 5p 3u") aob_reader.skip_bytes(stuffing_length) if (sync_bytes != 0x1BA): raise InvalidDVDA(_(u"invalid AOB sync bytes")) if ((marker1 != 1) or (marker2 != 1) or (marker3 != 1) or (marker4 != 1) or (marker5 != 1) or (marker6 != 3)): raise InvalidDVDA(_(u"invalid AOB marker bits")) packet_pts = ((current_pts_high << 30) | (current_pts_mid << 15) | current_pts_low) #skip packets until one with a stream ID of 0xBD is found (start_code, stream_id, packet_length) = aob_reader.parse("24u 8u 16u") if (start_code != 1): raise InvalidDVDA(_(u"invalid AOB packet start code")) while (stream_id != 0xBD): aob_reader.skip_bytes(packet_length) (start_code, stream_id, packet_length) = aob_reader.parse("24u 8u 16u") if (start_code != 1): raise InvalidDVDA(_(u"invalid AOB packet start code")) #parse the PCM/MLP header in the packet data (pad1_size,) = aob_reader.parse("16p 8u") aob_reader.skip_bytes(pad1_size) (stream_id, crc) = aob_reader.parse("8u 8u 8p") if (stream_id == 0xA0): #PCM #read a PCM reader (pad2_size, first_audio_frame, padding2, group1_bps, group2_bps, group1_sample_rate, group2_sample_rate, padding3, channel_assignment) = aob_reader.parse( "8u 16u 8u 4u 4u 4u 4u 8u 8u") else: #MLP aob_reader.skip_bytes(aob_reader.read(8)) #skip pad2 #read a total frame size + MLP major sync header (total_frame_size, sync_words, stream_type, group1_bps, group2_bps, group1_sample_rate, group2_sample_rate, unknown1, channel_assignment, unknown2) = aob_reader.parse( "4p 12u 16p 24u 8u 4u 4u 4u 4u 11u 5u 48u") #return the values indicated by the header self.sample_rate = DVDATrack.SAMPLE_RATE[group1_sample_rate] self.channels = DVDATrack.CHANNELS[channel_assignment] self.channel_mask = DVDATrack.CHANNEL_MASK[channel_assignment] self.bits_per_sample = DVDATrack.BITS_PER_SAMPLE[group1_bps] self.stream_id = stream_id finally: aob_file.close()
class FreeDB: """A class for performing queries on a FreeDB or compatible server. This operates using the original FreeDB client-server protocol.""" LINE = re.compile(r'\d\d\d\s.+') def __init__(self, server, port, messenger): """server is a string, port is an int, messenger is a Messenger. Queries are sent to the server, and output to the messenger.""" self.server = server self.port = port self.socket = None self.r = None self.w = None self.messenger = messenger def connect(self): """Performs the initial connection.""" import socket try: self.messenger.info(_(u"Connecting to \"%s\"") % (self.server)) self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect((self.server, self.port)) self.r = self.socket.makefile("rb") self.w = self.socket.makefile("wb") (code, msg) = self.read() # the welcome message if (code == 201): self.messenger.info(_(u"Connected ... attempting to login")) else: self.r.close() self.w.close() self.socket.close() raise FreeDBException(_(u"Invalid hello message")) self.write("cddb hello user %s %s %s" % \ (socket.getfqdn(), "audiotools", VERSION)) (code, msg) = self.read() # the handshake successful message if (code != 200): self.r.close() self.w.close() self.socket.close() raise FreeDBException(_(u"Handshake unsuccessful")) self.write("proto 6") (code, msg) = self.read() # the protocol successful message if ((code != 200) and (code != 201)): self.r.close() self.w.close() self.socket.close() raise FreeDBException(_(u"Protocol change unsuccessful")) except socket.error, err: raise FreeDBException(err[1])