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