1# -*- coding: utf-8 -*-
2# Copyright (C) 2005  Joe Wreschnig
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8
9"""Read and write FLAC Vorbis comments and stream information.
10
11Read more about FLAC at http://flac.sourceforge.net.
12
13FLAC supports arbitrary metadata blocks. The two most interesting ones
14are the FLAC stream information block, and the Vorbis comment block;
15these are also the only ones Mutagen can currently read.
16
17This module does not handle Ogg FLAC files.
18
19Based off documentation available at
20http://flac.sourceforge.net/format.html
21"""
22
23__all__ = ["FLAC", "Open", "delete"]
24
25import struct
26from ._vorbis import VCommentDict
27import mutagen
28
29from ._compat import cBytesIO, endswith, chr_, xrange
30from mutagen._util import resize_bytes, MutagenError, get_size, loadfile, \
31    convert_error
32from mutagen._tags import PaddingInfo
33from mutagen.id3._util import BitPaddedInt
34from functools import reduce
35
36
37class error(MutagenError):
38    pass
39
40
41class FLACNoHeaderError(error):
42    pass
43
44
45class FLACVorbisError(ValueError, error):
46    pass
47
48
49def to_int_be(data):
50    """Convert an arbitrarily-long string to a long using big-endian
51    byte order."""
52    return reduce(lambda a, b: (a << 8) + b, bytearray(data), 0)
53
54
55class StrictFileObject(object):
56    """Wraps a file-like object and raises an exception if the requested
57    amount of data to read isn't returned."""
58
59    def __init__(self, fileobj):
60        self._fileobj = fileobj
61        for m in ["close", "tell", "seek", "write", "name", "flush",
62                  "truncate"]:
63            if hasattr(fileobj, m):
64                setattr(self, m, getattr(fileobj, m))
65
66    def read(self, size=-1):
67        data = self._fileobj.read(size)
68        if size >= 0 and len(data) != size:
69            raise error("file said %d bytes, read %d bytes" % (
70                        size, len(data)))
71        return data
72
73    def tryread(self, *args):
74        return self._fileobj.read(*args)
75
76
77class MetadataBlock(object):
78    """A generic block of FLAC metadata.
79
80    This class is extended by specific used as an ancestor for more specific
81    blocks, and also as a container for data blobs of unknown blocks.
82
83    Attributes:
84        data (`bytes`): raw binary data for this block
85    """
86
87    _distrust_size = False
88    """For block types setting this, we don't trust the size field and
89    use the size of the content instead."""
90
91    _invalid_overflow_size = -1
92    """In case the real size was bigger than what is representable by the
93    24 bit size field, we save the wrong specified size here. This can
94    only be set if _distrust_size is True"""
95
96    _MAX_SIZE = 2 ** 24 - 1
97
98    def __init__(self, data):
99        """Parse the given data string or file-like as a metadata block.
100        The metadata header should not be included."""
101        if data is not None:
102            if not isinstance(data, StrictFileObject):
103                if isinstance(data, bytes):
104                    data = cBytesIO(data)
105                elif not hasattr(data, 'read'):
106                    raise TypeError(
107                        "StreamInfo requires string data or a file-like")
108                data = StrictFileObject(data)
109            self.load(data)
110
111    def load(self, data):
112        self.data = data.read()
113
114    def write(self):
115        return self.data
116
117    @classmethod
118    def _writeblock(cls, block, is_last=False):
119        """Returns the block content + header.
120
121        Raises error.
122        """
123
124        data = bytearray()
125        code = (block.code | 128) if is_last else block.code
126        datum = block.write()
127        size = len(datum)
128        if size > cls._MAX_SIZE:
129            if block._distrust_size and block._invalid_overflow_size != -1:
130                # The original size of this block was (1) wrong and (2)
131                # the real size doesn't allow us to save the file
132                # according to the spec (too big for 24 bit uint). Instead
133                # simply write back the original wrong size.. at least
134                # we don't make the file more "broken" as it is.
135                size = block._invalid_overflow_size
136            else:
137                raise error("block is too long to write")
138        assert not size > cls._MAX_SIZE
139        length = struct.pack(">I", size)[-3:]
140        data.append(code)
141        data += length
142        data += datum
143        return data
144
145    @classmethod
146    def _writeblocks(cls, blocks, available, cont_size, padding_func):
147        """Render metadata block as a byte string."""
148
149        # write everything except padding
150        data = bytearray()
151        for block in blocks:
152            if isinstance(block, Padding):
153                continue
154            data += cls._writeblock(block)
155        blockssize = len(data)
156
157        # take the padding overhead into account. we always add one
158        # to make things simple.
159        padding_block = Padding()
160        blockssize += len(cls._writeblock(padding_block))
161
162        # finally add a padding block
163        info = PaddingInfo(available - blockssize, cont_size)
164        padding_block.length = min(info._get_padding(padding_func),
165                                   cls._MAX_SIZE)
166        data += cls._writeblock(padding_block, is_last=True)
167
168        return data
169
170
171class StreamInfo(MetadataBlock, mutagen.StreamInfo):
172    """StreamInfo()
173
174    FLAC stream information.
175
176    This contains information about the audio data in the FLAC file.
177    Unlike most stream information objects in Mutagen, changes to this
178    one will rewritten to the file when it is saved. Unless you are
179    actually changing the audio stream itself, don't change any
180    attributes of this block.
181
182    Attributes:
183        min_blocksize (`int`): minimum audio block size
184        max_blocksize (`int`): maximum audio block size
185        sample_rate (`int`): audio sample rate in Hz
186        channels (`int`): audio channels (1 for mono, 2 for stereo)
187        bits_per_sample (`int`): bits per sample
188        total_samples (`int`): total samples in file
189        length (`float`): audio length in seconds
190        bitrate (`int`): bitrate in bits per second, as an int
191    """
192
193    code = 0
194    bitrate = 0
195
196    def __eq__(self, other):
197        try:
198            return (self.min_blocksize == other.min_blocksize and
199                    self.max_blocksize == other.max_blocksize and
200                    self.sample_rate == other.sample_rate and
201                    self.channels == other.channels and
202                    self.bits_per_sample == other.bits_per_sample and
203                    self.total_samples == other.total_samples)
204        except Exception:
205            return False
206
207    __hash__ = MetadataBlock.__hash__
208
209    def load(self, data):
210        self.min_blocksize = int(to_int_be(data.read(2)))
211        self.max_blocksize = int(to_int_be(data.read(2)))
212        self.min_framesize = int(to_int_be(data.read(3)))
213        self.max_framesize = int(to_int_be(data.read(3)))
214        # first 16 bits of sample rate
215        sample_first = to_int_be(data.read(2))
216        # last 4 bits of sample rate, 3 of channels, first 1 of bits/sample
217        sample_channels_bps = to_int_be(data.read(1))
218        # last 4 of bits/sample, 36 of total samples
219        bps_total = to_int_be(data.read(5))
220
221        sample_tail = sample_channels_bps >> 4
222        self.sample_rate = int((sample_first << 4) + sample_tail)
223        if not self.sample_rate:
224            raise error("A sample rate value of 0 is invalid")
225        self.channels = int(((sample_channels_bps >> 1) & 7) + 1)
226        bps_tail = bps_total >> 36
227        bps_head = (sample_channels_bps & 1) << 4
228        self.bits_per_sample = int(bps_head + bps_tail + 1)
229        self.total_samples = bps_total & 0xFFFFFFFFF
230        self.length = self.total_samples / float(self.sample_rate)
231
232        self.md5_signature = to_int_be(data.read(16))
233
234    def write(self):
235        f = cBytesIO()
236        f.write(struct.pack(">I", self.min_blocksize)[-2:])
237        f.write(struct.pack(">I", self.max_blocksize)[-2:])
238        f.write(struct.pack(">I", self.min_framesize)[-3:])
239        f.write(struct.pack(">I", self.max_framesize)[-3:])
240
241        # first 16 bits of sample rate
242        f.write(struct.pack(">I", self.sample_rate >> 4)[-2:])
243        # 4 bits sample, 3 channel, 1 bps
244        byte = (self.sample_rate & 0xF) << 4
245        byte += ((self.channels - 1) & 7) << 1
246        byte += ((self.bits_per_sample - 1) >> 4) & 1
247        f.write(chr_(byte))
248        # 4 bits of bps, 4 of sample count
249        byte = ((self.bits_per_sample - 1) & 0xF) << 4
250        byte += (self.total_samples >> 32) & 0xF
251        f.write(chr_(byte))
252        # last 32 of sample count
253        f.write(struct.pack(">I", self.total_samples & 0xFFFFFFFF))
254        # MD5 signature
255        sig = self.md5_signature
256        f.write(struct.pack(
257            ">4I", (sig >> 96) & 0xFFFFFFFF, (sig >> 64) & 0xFFFFFFFF,
258            (sig >> 32) & 0xFFFFFFFF, sig & 0xFFFFFFFF))
259        return f.getvalue()
260
261    def pprint(self):
262        return u"FLAC, %.2f seconds, %d Hz" % (self.length, self.sample_rate)
263
264
265class SeekPoint(tuple):
266    """SeekPoint()
267
268    A single seek point in a FLAC file.
269
270    Placeholder seek points have first_sample of 0xFFFFFFFFFFFFFFFFL,
271    and byte_offset and num_samples undefined. Seek points must be
272    sorted in ascending order by first_sample number. Seek points must
273    be unique by first_sample number, except for placeholder
274    points. Placeholder points must occur last in the table and there
275    may be any number of them.
276
277    Attributes:
278        first_sample (`int`): sample number of first sample in the target frame
279        byte_offset (`int`): offset from first frame to target frame
280        num_samples (`int`): number of samples in target frame
281    """
282
283    def __new__(cls, first_sample, byte_offset, num_samples):
284        return super(cls, SeekPoint).__new__(
285            cls, (first_sample, byte_offset, num_samples))
286
287    first_sample = property(lambda self: self[0])
288    byte_offset = property(lambda self: self[1])
289    num_samples = property(lambda self: self[2])
290
291
292class SeekTable(MetadataBlock):
293    """Read and write FLAC seek tables.
294
295    Attributes:
296        seekpoints: list of SeekPoint objects
297    """
298
299    __SEEKPOINT_FORMAT = '>QQH'
300    __SEEKPOINT_SIZE = struct.calcsize(__SEEKPOINT_FORMAT)
301
302    code = 3
303
304    def __init__(self, data):
305        self.seekpoints = []
306        super(SeekTable, self).__init__(data)
307
308    def __eq__(self, other):
309        try:
310            return (self.seekpoints == other.seekpoints)
311        except (AttributeError, TypeError):
312            return False
313
314    __hash__ = MetadataBlock.__hash__
315
316    def load(self, data):
317        self.seekpoints = []
318        sp = data.tryread(self.__SEEKPOINT_SIZE)
319        while len(sp) == self.__SEEKPOINT_SIZE:
320            self.seekpoints.append(SeekPoint(
321                *struct.unpack(self.__SEEKPOINT_FORMAT, sp)))
322            sp = data.tryread(self.__SEEKPOINT_SIZE)
323
324    def write(self):
325        f = cBytesIO()
326        for seekpoint in self.seekpoints:
327            packed = struct.pack(
328                self.__SEEKPOINT_FORMAT,
329                seekpoint.first_sample, seekpoint.byte_offset,
330                seekpoint.num_samples)
331            f.write(packed)
332        return f.getvalue()
333
334    def __repr__(self):
335        return "<%s seekpoints=%r>" % (type(self).__name__, self.seekpoints)
336
337
338class VCFLACDict(VCommentDict):
339    """VCFLACDict()
340
341    Read and write FLAC Vorbis comments.
342
343    FLACs don't use the framing bit at the end of the comment block.
344    So this extends VCommentDict to not use the framing bit.
345    """
346
347    code = 4
348    _distrust_size = True
349
350    def load(self, data, errors='replace', framing=False):
351        super(VCFLACDict, self).load(data, errors=errors, framing=framing)
352
353    def write(self, framing=False):
354        return super(VCFLACDict, self).write(framing=framing)
355
356
357class CueSheetTrackIndex(tuple):
358    """CueSheetTrackIndex(index_number, index_offset)
359
360    Index for a track in a cuesheet.
361
362    For CD-DA, an index_number of 0 corresponds to the track
363    pre-gap. The first index in a track must have a number of 0 or 1,
364    and subsequently, index_numbers must increase by 1. Index_numbers
365    must be unique within a track. And index_offset must be evenly
366    divisible by 588 samples.
367
368    Attributes:
369        index_number (`int`): index point number
370        index_offset (`int`): offset in samples from track start
371    """
372
373    def __new__(cls, index_number, index_offset):
374        return super(cls, CueSheetTrackIndex).__new__(
375            cls, (index_number, index_offset))
376
377    index_number = property(lambda self: self[0])
378    index_offset = property(lambda self: self[1])
379
380
381class CueSheetTrack(object):
382    """CueSheetTrack()
383
384    A track in a cuesheet.
385
386    For CD-DA, track_numbers must be 1-99, or 170 for the
387    lead-out. Track_numbers must be unique within a cue sheet. There
388    must be atleast one index in every track except the lead-out track
389    which must have none.
390
391    Attributes:
392        track_number (`int`): track number
393        start_offset (`int`): track offset in samples from start of FLAC stream
394        isrc (`mutagen.text`): ISRC code, exactly 12 characters
395        type (`int`): 0 for audio, 1 for digital data
396        pre_emphasis (`bool`): true if the track is recorded with pre-emphasis
397        indexes (list[CueSheetTrackIndex]):
398            list of CueSheetTrackIndex objects
399    """
400
401    def __init__(self, track_number, start_offset, isrc='', type_=0,
402                 pre_emphasis=False):
403        self.track_number = track_number
404        self.start_offset = start_offset
405        self.isrc = isrc
406        self.type = type_
407        self.pre_emphasis = pre_emphasis
408        self.indexes = []
409
410    def __eq__(self, other):
411        try:
412            return (self.track_number == other.track_number and
413                    self.start_offset == other.start_offset and
414                    self.isrc == other.isrc and
415                    self.type == other.type and
416                    self.pre_emphasis == other.pre_emphasis and
417                    self.indexes == other.indexes)
418        except (AttributeError, TypeError):
419            return False
420
421    __hash__ = object.__hash__
422
423    def __repr__(self):
424        return (("<%s number=%r, offset=%d, isrc=%r, type=%r, "
425                "pre_emphasis=%r, indexes=%r)>") %
426                (type(self).__name__, self.track_number, self.start_offset,
427                 self.isrc, self.type, self.pre_emphasis, self.indexes))
428
429
430class CueSheet(MetadataBlock):
431    """CueSheet()
432
433    Read and write FLAC embedded cue sheets.
434
435    Number of tracks should be from 1 to 100. There should always be
436    exactly one lead-out track and that track must be the last track
437    in the cue sheet.
438
439    Attributes:
440        media_catalog_number (`mutagen.text`): media catalog number in ASCII,
441            up to 128 characters
442        lead_in_samples (`int`): number of lead-in samples
443        compact_disc (`bool`): true if the cuesheet corresponds to a
444            compact disc
445        tracks (list[CueSheetTrack]):
446            list of CueSheetTrack objects
447        lead_out (`CueSheetTrack` or `None`):
448            lead-out as CueSheetTrack or None if lead-out was not found
449    """
450
451    __CUESHEET_FORMAT = '>128sQB258xB'
452    __CUESHEET_SIZE = struct.calcsize(__CUESHEET_FORMAT)
453    __CUESHEET_TRACK_FORMAT = '>QB12sB13xB'
454    __CUESHEET_TRACK_SIZE = struct.calcsize(__CUESHEET_TRACK_FORMAT)
455    __CUESHEET_TRACKINDEX_FORMAT = '>QB3x'
456    __CUESHEET_TRACKINDEX_SIZE = struct.calcsize(__CUESHEET_TRACKINDEX_FORMAT)
457
458    code = 5
459
460    media_catalog_number = b''
461    lead_in_samples = 88200
462    compact_disc = True
463
464    def __init__(self, data):
465        self.tracks = []
466        super(CueSheet, self).__init__(data)
467
468    def __eq__(self, other):
469        try:
470            return (self.media_catalog_number == other.media_catalog_number and
471                    self.lead_in_samples == other.lead_in_samples and
472                    self.compact_disc == other.compact_disc and
473                    self.tracks == other.tracks)
474        except (AttributeError, TypeError):
475            return False
476
477    __hash__ = MetadataBlock.__hash__
478
479    def load(self, data):
480        header = data.read(self.__CUESHEET_SIZE)
481        media_catalog_number, lead_in_samples, flags, num_tracks = \
482            struct.unpack(self.__CUESHEET_FORMAT, header)
483        self.media_catalog_number = media_catalog_number.rstrip(b'\0')
484        self.lead_in_samples = lead_in_samples
485        self.compact_disc = bool(flags & 0x80)
486        self.tracks = []
487        for i in xrange(num_tracks):
488            track = data.read(self.__CUESHEET_TRACK_SIZE)
489            start_offset, track_number, isrc_padded, flags, num_indexes = \
490                struct.unpack(self.__CUESHEET_TRACK_FORMAT, track)
491            isrc = isrc_padded.rstrip(b'\0')
492            type_ = (flags & 0x80) >> 7
493            pre_emphasis = bool(flags & 0x40)
494            val = CueSheetTrack(
495                track_number, start_offset, isrc, type_, pre_emphasis)
496            for j in xrange(num_indexes):
497                index = data.read(self.__CUESHEET_TRACKINDEX_SIZE)
498                index_offset, index_number = struct.unpack(
499                    self.__CUESHEET_TRACKINDEX_FORMAT, index)
500                val.indexes.append(
501                    CueSheetTrackIndex(index_number, index_offset))
502            self.tracks.append(val)
503
504    def write(self):
505        f = cBytesIO()
506        flags = 0
507        if self.compact_disc:
508            flags |= 0x80
509        packed = struct.pack(
510            self.__CUESHEET_FORMAT, self.media_catalog_number,
511            self.lead_in_samples, flags, len(self.tracks))
512        f.write(packed)
513        for track in self.tracks:
514            track_flags = 0
515            track_flags |= (track.type & 1) << 7
516            if track.pre_emphasis:
517                track_flags |= 0x40
518            track_packed = struct.pack(
519                self.__CUESHEET_TRACK_FORMAT, track.start_offset,
520                track.track_number, track.isrc, track_flags,
521                len(track.indexes))
522            f.write(track_packed)
523            for index in track.indexes:
524                index_packed = struct.pack(
525                    self.__CUESHEET_TRACKINDEX_FORMAT,
526                    index.index_offset, index.index_number)
527                f.write(index_packed)
528        return f.getvalue()
529
530    def __repr__(self):
531        return (("<%s media_catalog_number=%r, lead_in=%r, compact_disc=%r, "
532                 "tracks=%r>") %
533                (type(self).__name__, self.media_catalog_number,
534                 self.lead_in_samples, self.compact_disc, self.tracks))
535
536
537class Picture(MetadataBlock):
538    """Picture()
539
540    Read and write FLAC embed pictures.
541
542    .. currentmodule:: mutagen
543
544    Attributes:
545        type (`id3.PictureType`): picture type
546            (same as types for ID3 APIC frames)
547        mime (`text`): MIME type of the picture
548        desc (`text`): picture's description
549        width (`int`): width in pixels
550        height (`int`): height in pixels
551        depth (`int`): color depth in bits-per-pixel
552        colors (`int`): number of colors for indexed palettes (like GIF),
553            0 for non-indexed
554        data (`bytes`): picture data
555
556    To create a picture from file (in order to add to a FLAC file),
557    instantiate this object without passing anything to the constructor and
558    then set the properties manually::
559
560        p = Picture()
561
562        with open("Folder.jpg", "rb") as f:
563            pic.data = f.read()
564
565        pic.type = id3.PictureType.COVER_FRONT
566        pic.mime = u"image/jpeg"
567        pic.width = 500
568        pic.height = 500
569        pic.depth = 16 # color depth
570    """
571
572    code = 6
573    _distrust_size = True
574
575    def __init__(self, data=None):
576        self.type = 0
577        self.mime = u''
578        self.desc = u''
579        self.width = 0
580        self.height = 0
581        self.depth = 0
582        self.colors = 0
583        self.data = b''
584        super(Picture, self).__init__(data)
585
586    def __eq__(self, other):
587        try:
588            return (self.type == other.type and
589                    self.mime == other.mime and
590                    self.desc == other.desc and
591                    self.width == other.width and
592                    self.height == other.height and
593                    self.depth == other.depth and
594                    self.colors == other.colors and
595                    self.data == other.data)
596        except (AttributeError, TypeError):
597            return False
598
599    __hash__ = MetadataBlock.__hash__
600
601    def load(self, data):
602        self.type, length = struct.unpack('>2I', data.read(8))
603        self.mime = data.read(length).decode('UTF-8', 'replace')
604        length, = struct.unpack('>I', data.read(4))
605        self.desc = data.read(length).decode('UTF-8', 'replace')
606        (self.width, self.height, self.depth,
607         self.colors, length) = struct.unpack('>5I', data.read(20))
608        self.data = data.read(length)
609
610    def write(self):
611        f = cBytesIO()
612        mime = self.mime.encode('UTF-8')
613        f.write(struct.pack('>2I', self.type, len(mime)))
614        f.write(mime)
615        desc = self.desc.encode('UTF-8')
616        f.write(struct.pack('>I', len(desc)))
617        f.write(desc)
618        f.write(struct.pack('>5I', self.width, self.height, self.depth,
619                            self.colors, len(self.data)))
620        f.write(self.data)
621        return f.getvalue()
622
623    def __repr__(self):
624        return "<%s '%s' (%d bytes)>" % (type(self).__name__, self.mime,
625                                         len(self.data))
626
627
628class Padding(MetadataBlock):
629    """Padding()
630
631    Empty padding space for metadata blocks.
632
633    To avoid rewriting the entire FLAC file when editing comments,
634    metadata is often padded. Padding should occur at the end, and no
635    more than one padding block should be in any FLAC file.
636
637    Attributes:
638        length (`int`): length
639    """
640
641    code = 1
642
643    def __init__(self, data=b""):
644        super(Padding, self).__init__(data)
645
646    def load(self, data):
647        self.length = len(data.read())
648
649    def write(self):
650        try:
651            return b"\x00" * self.length
652        # On some 64 bit platforms this won't generate a MemoryError
653        # or OverflowError since you might have enough RAM, but it
654        # still generates a ValueError. On other 64 bit platforms,
655        # this will still succeed for extremely large values.
656        # Those should never happen in the real world, and if they
657        # do, writeblocks will catch it.
658        except (OverflowError, ValueError, MemoryError):
659            raise error("cannot write %d bytes" % self.length)
660
661    def __eq__(self, other):
662        return isinstance(other, Padding) and self.length == other.length
663
664    __hash__ = MetadataBlock.__hash__
665
666    def __repr__(self):
667        return "<%s (%d bytes)>" % (type(self).__name__, self.length)
668
669
670class FLAC(mutagen.FileType):
671    """FLAC(filething)
672
673    A FLAC audio file.
674
675    Args:
676        filething (filething)
677
678    Attributes:
679        cuesheet (`CueSheet`): if any or `None`
680        seektable (`SeekTable`): if any or `None`
681        pictures (list[Picture]): list of embedded pictures
682        info (`StreamInfo`)
683        tags (`mutagen._vorbis.VCommentDict`)
684    """
685
686    _mimes = ["audio/flac", "audio/x-flac", "application/x-flac"]
687
688    info = None
689    tags = None
690
691    METADATA_BLOCKS = [StreamInfo, Padding, None, SeekTable, VCFLACDict,
692                       CueSheet, Picture]
693    """Known metadata block types, indexed by ID."""
694
695    @staticmethod
696    def score(filename, fileobj, header_data):
697        return (header_data.startswith(b"fLaC") +
698                endswith(filename.lower(), ".flac") * 3)
699
700    def __read_metadata_block(self, fileobj):
701        byte = ord(fileobj.read(1))
702        size = to_int_be(fileobj.read(3))
703        code = byte & 0x7F
704        last_block = bool(byte & 0x80)
705
706        try:
707            block_type = self.METADATA_BLOCKS[code] or MetadataBlock
708        except IndexError:
709            block_type = MetadataBlock
710
711        if block_type._distrust_size:
712            # Some jackass is writing broken Metadata block length
713            # for Vorbis comment blocks, and the FLAC reference
714            # implementaton can parse them (mostly by accident),
715            # so we have to too.  Instead of parsing the size
716            # given, parse an actual Vorbis comment, leaving
717            # fileobj in the right position.
718            # https://github.com/quodlibet/mutagen/issues/52
719            # ..same for the Picture block:
720            # https://github.com/quodlibet/mutagen/issues/106
721            start = fileobj.tell()
722            block = block_type(fileobj)
723            real_size = fileobj.tell() - start
724            if real_size > MetadataBlock._MAX_SIZE:
725                block._invalid_overflow_size = size
726        else:
727            data = fileobj.read(size)
728            block = block_type(data)
729        block.code = code
730
731        if block.code == VCFLACDict.code:
732            if self.tags is None:
733                self.tags = block
734            else:
735                raise FLACVorbisError("> 1 Vorbis comment block found")
736        elif block.code == CueSheet.code:
737            if self.cuesheet is None:
738                self.cuesheet = block
739            else:
740                raise error("> 1 CueSheet block found")
741        elif block.code == SeekTable.code:
742            if self.seektable is None:
743                self.seektable = block
744            else:
745                raise error("> 1 SeekTable block found")
746        self.metadata_blocks.append(block)
747        return not last_block
748
749    def add_tags(self):
750        """Add a Vorbis comment block to the file."""
751        if self.tags is None:
752            self.tags = VCFLACDict()
753            self.metadata_blocks.append(self.tags)
754        else:
755            raise FLACVorbisError("a Vorbis comment already exists")
756
757    add_vorbiscomment = add_tags
758
759    @loadfile(writable=True)
760    def delete(self, filething=None):
761        """Remove Vorbis comments from a file.
762
763        If no filename is given, the one most recently loaded is used.
764        """
765
766        if self.tags is not None:
767            self.metadata_blocks.remove(self.tags)
768            try:
769                self.save(filething, padding=lambda x: 0)
770            finally:
771                self.metadata_blocks.append(self.tags)
772            self.tags.clear()
773
774    vc = property(lambda s: s.tags, doc="Alias for tags; don't use this.")
775
776    @convert_error(IOError, error)
777    @loadfile()
778    def load(self, filething):
779        """Load file information from a filename."""
780
781        fileobj = filething.fileobj
782
783        self.metadata_blocks = []
784        self.tags = None
785        self.cuesheet = None
786        self.seektable = None
787
788        fileobj = StrictFileObject(fileobj)
789        self.__check_header(fileobj, filething.name)
790        while self.__read_metadata_block(fileobj):
791            pass
792
793        try:
794            self.metadata_blocks[0].length
795        except (AttributeError, IndexError):
796            raise FLACNoHeaderError("Stream info block not found")
797
798        if self.info.length:
799            start = fileobj.tell()
800            fileobj.seek(0, 2)
801            self.info.bitrate = int(
802                float(fileobj.tell() - start) * 8 / self.info.length)
803        else:
804            self.info.bitrate = 0
805
806    @property
807    def info(self):
808        return self.metadata_blocks[0]
809
810    def add_picture(self, picture):
811        """Add a new picture to the file.
812
813        Args:
814            picture (Picture)
815        """
816        self.metadata_blocks.append(picture)
817
818    def clear_pictures(self):
819        """Delete all pictures from the file."""
820
821        blocks = [b for b in self.metadata_blocks if b.code != Picture.code]
822        self.metadata_blocks = blocks
823
824    @property
825    def pictures(self):
826        """list[Picture]: List of embedded pictures"""
827
828        return [b for b in self.metadata_blocks if b.code == Picture.code]
829
830    @convert_error(IOError, error)
831    @loadfile(writable=True)
832    def save(self, filething=None, deleteid3=False, padding=None):
833        """Save metadata blocks to a file.
834
835        Args:
836            filething (filething)
837            deleteid3 (bool): delete id3 tags while at it
838            padding (:obj:`mutagen.PaddingFunction`)
839
840        If no filename is given, the one most recently loaded is used.
841        """
842
843        f = StrictFileObject(filething.fileobj)
844        header = self.__check_header(f, filething.name)
845        audio_offset = self.__find_audio_offset(f)
846        # "fLaC" and maybe ID3
847        available = audio_offset - header
848
849        # Delete ID3v2
850        if deleteid3 and header > 4:
851            available += header - 4
852            header = 4
853
854        content_size = get_size(f) - audio_offset
855        assert content_size >= 0
856        data = MetadataBlock._writeblocks(
857            self.metadata_blocks, available, content_size, padding)
858        data_size = len(data)
859
860        resize_bytes(filething.fileobj, available, data_size, header)
861        f.seek(header - 4)
862        f.write(b"fLaC")
863        f.write(data)
864
865        # Delete ID3v1
866        if deleteid3:
867            try:
868                f.seek(-128, 2)
869            except IOError:
870                pass
871            else:
872                if f.read(3) == b"TAG":
873                    f.seek(-128, 2)
874                    f.truncate()
875
876    def __find_audio_offset(self, fileobj):
877        byte = 0x00
878        while not (byte & 0x80):
879            byte = ord(fileobj.read(1))
880            size = to_int_be(fileobj.read(3))
881            try:
882                block_type = self.METADATA_BLOCKS[byte & 0x7F]
883            except IndexError:
884                block_type = None
885
886            if block_type and block_type._distrust_size:
887                # See comments in read_metadata_block; the size can't
888                # be trusted for Vorbis comment blocks and Picture block
889                block_type(fileobj)
890            else:
891                fileobj.read(size)
892        return fileobj.tell()
893
894    def __check_header(self, fileobj, name):
895        """Returns the offset of the flac block start
896        (skipping id3 tags if found). The passed fileobj will be advanced to
897        that offset as well.
898        """
899
900        size = 4
901        header = fileobj.read(4)
902        if header != b"fLaC":
903            size = None
904            if header[:3] == b"ID3":
905                size = 14 + BitPaddedInt(fileobj.read(6)[2:])
906                fileobj.seek(size - 4)
907                if fileobj.read(4) != b"fLaC":
908                    size = None
909        if size is None:
910            raise FLACNoHeaderError(
911                "%r is not a valid FLAC file" % name)
912        return size
913
914
915Open = FLAC
916
917
918@convert_error(IOError, error)
919@loadfile(method=False, writable=True)
920def delete(filething):
921    """Remove tags from a file.
922
923    Args:
924        filething (filething)
925    Raises:
926        mutagen.MutagenError
927    """
928
929    f = FLAC(filething)
930    filething.fileobj.seek(0)
931    f.delete(filething)
932