class __JPEG__(ImageMetrics): SEGMENT_HEADER = Con.Struct('segment_header', Con.Const(Con.Byte('header'), 0xFF), Con.Byte('type'), Con.If( lambda ctx: ctx['type'] not in (0xD8, 0xD9), Con.UBInt16('length'))) APP0 = Con.Struct('JFIF_segment_marker', Con.String('identifier', 5), Con.Byte('major_version'), Con.Byte('minor_version'), Con.Byte('density_units'), Con.UBInt16('x_density'), Con.UBInt16('y_density'), Con.Byte('thumbnail_width'), Con.Byte('thumbnail_height')) SOF = Con.Struct('start_of_frame', Con.Byte('data_precision'), Con.UBInt16('image_height'), Con.UBInt16('image_width'), Con.Byte('components')) def __init__(self, width, height, bits_per_pixel): ImageMetrics.__init__(self, width, height, bits_per_pixel, 0, u'image/jpeg') @classmethod def parse(cls, file): try: header = cls.SEGMENT_HEADER.parse_stream(file) if (header.type != 0xD8): raise InvalidJPEG(_(u'Invalid JPEG header')) segment = cls.SEGMENT_HEADER.parse_stream(file) while (segment.type != 0xD9): if (segment.type == 0xDA): break if (segment.type in (0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0XC5, 0xC6, 0xC7, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF)): # start of frame segment_data = cStringIO.StringIO( file.read(segment.length - 2)) frame0 = cls.SOF.parse_stream(segment_data) segment_data.close() return __JPEG__(width=frame0.image_width, height=frame0.image_height, bits_per_pixel=(frame0.data_precision * frame0.components)) else: file.seek(segment.length - 2, 1) segment = cls.SEGMENT_HEADER.parse_stream(file) raise InvalidJPEG(_(u'Start of frame not found')) except Con.ConstError: raise InvalidJPEG(_(u"Invalid JPEG segment marker at 0x%X") % \ (file.tell()))
class DiscID: """An object representing a 32 bit FreeDB disc ID value.""" DISCID = Con.Struct('discid', Con.UBInt8('digit_sum'), Con.UBInt16('length'), Con.UBInt8('track_count')) def __init__(self, tracks=[], offsets=None, length=None, lead_in=150): """Fields are as follows: tracks - a list of track lengths in CD frames offsets - a list of track offsets in CD frames length - the length of the entire disc in CD frames lead_in - the location of the first track on the CD, in frames These fields are all optional. One will presumably fill them with data later in that event. """ self.tracks = tracks self.__offsets__ = offsets self.__length__ = length self.__lead_in__ = lead_in @classmethod def from_cdda(cls, cdda): """Given a CDDA object, returns a populated DiscID. May raise ValueError if there are no audio tracks on the CD.""" tracks = list(cdda) if (len(tracks) < 1): raise ValueError(_(u"no audio tracks in CDDA object")) return cls(tracks=[t.length() for t in tracks], offsets=[t.offset() for t in tracks], length=cdda.length(), lead_in=tracks[0].offset()) def add(self, track): """Adds a new track length, in CD frames.""" self.tracks.append(track) def offsets(self): """Returns a list of calculated offset integers, from track lengths.""" if (self.__offsets__ is None): offsets = [self.__lead_in__] for track in self.tracks[0:-1]: offsets.append(track + offsets[-1]) return offsets else: return self.__offsets__ def length(self): """Returns the total length of the disc, in seconds.""" if (self.__length__ is None): return sum(self.tracks) else: return self.__length__ def idsuffix(self): """Returns a FreeDB disc ID suffix string. This is for making server queries.""" return str(len(self.tracks)) + " " + \ " ".join([str(offset) for offset in self.offsets()]) + \ " " + str((self.length() + self.__lead_in__) / 75) def __str__(self): def __count_digits__(i): if (i == 0): return 0 else: return (i % 10) + __count_digits__(i / 10) disc_id = Con.Container() disc_id.track_count = len(self.tracks) disc_id.length = self.length() / 75 disc_id.digit_sum = sum( [__count_digits__(o / 75) for o in self.offsets()]) % 0xFF return DiscID.DISCID.build(disc_id).encode('hex') def freedb_id(self): """Returns the entire FreeDB disc ID, including suffix.""" return str(self) + " " + self.idsuffix() def toxmcd(self, output): """Writes a newly created XMCD file to output. Its values are populated from this DiscID's fields.""" output.write( XMCD.from_tracks([ DummyAudioFile(length, None, i + 1) for (i, length) in enumerate(self.tracks) ]).to_string())