def undo(self, new_file): """updates new_file to its original state, if present in the undo database""" undo_checksum = OldUndoDB.checksum(new_file) if (undo_checksum in self.db.keys()): # copy the xdelta to a temporary file xdelta_f = tempfile.NamedTemporaryFile(suffix=".delta") xdelta_f.write(self.db[undo_checksum]) xdelta_f.flush() # patch the existing track to a temporary track old_track = tempfile.NamedTemporaryFile() try: if (subprocess.call([BIN["xdelta"], "patch", xdelta_f.name, new_file, old_track.name]) == 0): # copy the temporary track over the existing file f1 = open(old_track.name, 'rb') f2 = open(new_file, 'wb') transfer_data(f1.read, f2.write) f1.close() f2.close() return True else: raise IOError("error performing xdelta operation") finally: old_track.close() xdelta_f.close() else: return False
def delete_metadata(self): """deletes the track's MetaData this removes or unsets tags as necessary in order to remove all data raises IOError if unable to write the file""" import os from audiotools import (TemporaryFile, LimitedFileReader, transfer_data) #this works a lot like update_metadata #but without any new metadata to set if (not os.access(self.filename, os.W_OK)): raise IOError(self.filename) new_mp3 = TemporaryFile(self.filename) #get the original MP3 data old_mp3 = open(self.filename, "rb") MP3Audio.__find_last_mp3_frame__(old_mp3) data_end = old_mp3.tell() old_mp3.seek(0, 0) MP3Audio.__find_mp3_start__(old_mp3) data_start = old_mp3.tell() old_mp3 = LimitedFileReader(old_mp3, data_end - data_start) #write data to file transfer_data(old_mp3.read, new_mp3.write) #commit change to disk old_mp3.close() new_mp3.close()
def delete_metadata(self): """deletes the track's MetaData this removes or unsets tags as necessary in order to remove all data raises IOError if unable to write the file""" import os from audiotools import (transfer_data, LimitedFileReader, TemporaryFile) from audiotools.id3 import skip_id3v2_comment if (not os.access(self.filename, os.W_OK)): raise IOError(self.filename) # overwrite original with no tags attached old_tta = open(self.filename, "rb") skip_id3v2_comment(old_tta) old_tta = LimitedFileReader(old_tta, self.data_size()) new_tta = TemporaryFile(self.filename) transfer_data(old_tta.read, new_tta.write) old_tta.close() new_tta.close()
def add(self, old_file, new_file): """adds an undo entry for transforming new_file to old_file both are filename strings""" from io import BytesIO # perform xdelta between old and new track to temporary file delta_f = tempfile.NamedTemporaryFile(suffix=".delta") try: if (subprocess.call([BIN["xdelta"], "delta", new_file, old_file, delta_f.name]) != 2): # store the xdelta in our internal db f = open(delta_f.name, 'rb') data = BytesIO() transfer_data(f.read, data.write) f.close() self.db[OldUndoDB.checksum(new_file)] = data.getvalue() else: raise IOError("error performing xdelta operation") finally: delta_f.close()
def add(self, old_file, new_file): """adds an undo entry for transforming new_file to old_file both are filename strings""" from io import BytesIO # perform xdelta between old and new track to temporary file delta_f = tempfile.NamedTemporaryFile(suffix=".delta") try: if (subprocess.call([ BIN["xdelta"], "delta", new_file, old_file, delta_f.name ]) != 2): # store the xdelta in our internal db f = open(delta_f.name, 'rb') data = BytesIO() transfer_data(f.read, data.write) f.close() self.db[OldUndoDB.checksum(new_file)] = data.getvalue() else: raise IOError("error performing xdelta operation") finally: delta_f.close()
def to_wave(self, wave_filename): """writes the contents of this file to the given .wav filename string raises EncodingError if some error occurs during decoding""" from audiotools import BIN from audiotools import transfer_data import subprocess import os if (self.filename.endswith(".ape")): devnull = open(os.devnull, "wb") sub = subprocess.Popen( [BIN['mac'], self.filename, wave_filename, '-d'], stdout=devnull, stderr=devnull) sub.wait() devnull.close() else: devnull = open(os.devnull, 'ab') import tempfile ape = tempfile.NamedTemporaryFile(suffix='.ape') f = open(self.filename, 'rb') transfer_data(f.read, ape.write) f.close() ape.flush() sub = subprocess.Popen([BIN['mac'], ape.name, wave_filename, '-d'], stdout=devnull, stderr=devnull) sub.wait() ape.close() devnull.close()
def delete_metadata(self): """Deletes the track's MetaData. This removes or unsets tags as necessary in order to remove all data. Raises IOError if unable to write the file.""" import tempfile new_aiff = tempfile.TemporaryFile() new_aiff.seek(12, 0) for (chunk_id, chunk_length, chunk_file) in self.chunk_files(): if (chunk_id != 'ID3 '): new_aiff.write( self.CHUNK_HEADER.build( Con.Container(chunk_id=chunk_id, chunk_length=chunk_length))) transfer_data(chunk_file.read, new_aiff.write) header = Con.Container(aiff_id='FORM', aiff_size=new_aiff.tell() - 8, aiff_type='AIFF') new_aiff.seek(0, 0) new_aiff.write(self.AIFF_HEADER.build(header)) new_aiff.seek(0, 0) f = open(self.filename, 'wb') transfer_data(new_aiff.read, f.write) new_aiff.close() f.close()
def undo_from_backup(track, undo_db, messenger): undo_checksum = checksum(track.filename) if (undo_db.has_key(undo_checksum)): #copy the xdelta to a temporary file xdelta_f = tempfile.NamedTemporaryFile(suffix=".delta") xdelta_f.write(undo_db[undo_checksum]) xdelta_f.flush() #patch the existing track to a temporary track old_track = tempfile.NamedTemporaryFile(suffix="." + track.SUFFIX) if (subprocess.call([ audiotools.BIN["xdelta"], "patch", xdelta_f.name, track.filename, old_track.name ]) == 0): #copy the temporary track over the existing file f1 = open(old_track.name, 'rb') f2 = open(track.filename, 'wb') audiotools.transfer_data(f1.read, f2.write) f1.close() f2.close() old_track.close() xdelta_f.close() messenger.info(_(u"Restored: %s") % \ (messenger.filename(track.filename)))
def delete_metadata(self): """Deletes the track's MetaData. This removes or unsets tags as necessary in order to remove all data. Raises IOError if unable to write the file.""" def chunk_filter(chunks): for (chunk_id, chunk_size, chunk_data) in chunks: if (chunk_id == 'ID3 '): continue else: yield (chunk_id, chunk_size, chunk_data) import tempfile import tempfile new_aiff = tempfile.NamedTemporaryFile(suffix=self.SUFFIX) self.__class__.aiff_from_chunks(new_aiff.name, chunk_filter(self.chunks())) new_file = open(new_aiff.name, 'rb') old_file = open(self.filename, 'wb') transfer_data(new_file.read, old_file.write) old_file.close() new_file.close()
def fix_id3_preserve_originals(self, tempFilePath): f = file(self.filename, 'rb') #figure out where the start and end points of the FLAC file are audiotools.ID3v2Comment.skip(f) flac_start = f.tell() f.seek(-128, 2) if (f.read(3) == 'TAG'): f.seek(-3, 1) flac_end = f.tell() else: f.seek(0, 2) flac_end = f.tell() #copy the FLAC data to a temporary location temp = tempfile.TemporaryFile() f.seek(flac_start, 0) reader = audiotools.__capped_stream_reader__(f, flac_end - flac_start) audiotools.transfer_data(reader.read, temp.write) #rewrite the original FLAC with our temporary data temp.seek(0, 0) f.close() f = file(tempFilePath, 'wb') audiotools.transfer_data(temp.read, f.write) temp.close() f.close() returnValue = audiotools.open(tempFilePath) return returnValue
def delete_metadata(self): """Deletes the track's MetaData. This removes or unsets tags as necessary in order to remove all data. Raises IOError if unable to write the file.""" import tempfile new_aiff = tempfile.TemporaryFile() new_aiff.seek(12, 0) for (chunk_id, chunk_length, chunk_file) in self.chunk_files(): if (chunk_id != 'ID3 '): new_aiff.write(self.CHUNK_HEADER.build( Con.Container(chunk_id=chunk_id, chunk_length=chunk_length))) transfer_data(chunk_file.read, new_aiff.write) header = Con.Container( aiff_id='FORM', aiff_size=new_aiff.tell() - 8, aiff_type='AIFF') new_aiff.seek(0, 0) new_aiff.write(self.AIFF_HEADER.build(header)) new_aiff.seek(0, 0) f = open(self.filename, 'wb') transfer_data(new_aiff.read, f.write) new_aiff.close() f.close()
def undo(self, new_file): """Updates new_file to its original state, if present in the undo database.""" undo_checksum = OldUndoDB.checksum(new_file) if (undo_checksum in self.db.keys()): #copy the xdelta to a temporary file xdelta_f = tempfile.NamedTemporaryFile(suffix=".delta") xdelta_f.write(self.db[undo_checksum]) xdelta_f.flush() #patch the existing track to a temporary track old_track = tempfile.NamedTemporaryFile() try: if (subprocess.call([ BIN["xdelta"], "patch", xdelta_f.name, new_file, old_track.name ]) == 0): #copy the temporary track over the existing file f1 = open(old_track.name, 'rb') f2 = open(new_file, 'wb') transfer_data(f1.read, f2.write) f1.close() f2.close() return True else: raise IOError("error performing xdelta operation") finally: old_track.close() xdelta_f.close() else: return False
def to_wave(self, wave_filename): """Writes the contents of this file to the given .wav filename string. Raises EncodingError if some error occurs during decoding.""" if (self.filename.endswith(".ape")): devnull = file(os.devnull, "wb") sub = subprocess.Popen([BIN['mac'], self.filename, wave_filename, '-d'], stdout=devnull, stderr=devnull) sub.wait() devnull.close() else: devnull = file(os.devnull, 'ab') import tempfile ape = tempfile.NamedTemporaryFile(suffix='.ape') f = file(self.filename, 'rb') transfer_data(f.read, ape.write) f.close() ape.flush() sub = subprocess.Popen([BIN['mac'], ape.name, wave_filename, '-d'], stdout=devnull, stderr=devnull) sub.wait() ape.close() devnull.close()
def __help_output__(cls): import cStringIO help_data = cStringIO.StringIO() sub = subprocess.Popen([BIN['lame'],'--help'], stdout=subprocess.PIPE) transfer_data(sub.stdout.read,help_data.write) sub.wait() return help_data.getvalue()
def checksum(path): f = open(path, "rb") c = sha1("") try: audiotools.transfer_data(f.read, c.update) return c.hexdigest() finally: f.close()
def __help_output__(cls): import cStringIO help_data = cStringIO.StringIO() sub = subprocess.Popen([BIN['lame'], '--help'], stdout=subprocess.PIPE) transfer_data(sub.stdout.read, help_data.write) sub.wait() return help_data.getvalue()
def to_pcm(self): #if mpg123 is available, use that for decoding if (BIN.can_execute(BIN["mpg123"])): sub = subprocess.Popen([BIN["mpg123"],"-qs",self.filename], stdout=subprocess.PIPE, stderr=file(os.devnull,"a")) return PCMReader(sub.stdout, sample_rate=self.sample_rate(), channels=self.channels(), bits_per_sample=16, channel_mask=int(ChannelMask.from_channels(self.channels())), process=sub, big_endian=BIG_ENDIAN) else: #if not, use LAME for decoding if (self.filename.endswith("." + self.SUFFIX)): if (BIG_ENDIAN): endian = ['-x'] else: endian = [] sub = subprocess.Popen([BIN['lame']] + endian + \ ["--decode","-t","--quiet", self.filename,"-"], stdout=subprocess.PIPE) return PCMReader(sub.stdout, sample_rate=self.sample_rate(), channels=self.channels(), bits_per_sample=16, channel_mask=int(ChannelMask.from_channels(self.channels())), process=sub) else: import tempfile from audiotools import TempWaveReader #copy our file to one that ends with .mp3 tempmp3 = tempfile.NamedTemporaryFile(suffix='.' + self.SUFFIX) f = open(self.filename,'rb') transfer_data(f.read,tempmp3.write) f.close() tempmp3.flush() #decode the mp3 file to a WAVE file wave = tempfile.NamedTemporaryFile(suffix='.wav') returnval = subprocess.call([BIN['lame'],"--decode","--quiet", tempmp3.name,wave.name]) tempmp3.close() if (returnval == 0): #return WAVE file as a stream wave.seek(0,0) return TempWaveReader(wave) else: return PCMReaderError(None, sample_rate=self.sample_rate(), channels=self.channels(), bits_per_sample=16)
def checksum(cls, filename): """Returns the SHA1 checksum of the filename's contents.""" f = open(filename, "rb") c = sha1("") try: transfer_data(f.read, c.update) return c.hexdigest() finally: f.close()
def checksum(cls, filename): """returns the SHA1 checksum of the filename's contents""" f = open(filename, "rb") c = sha1("") try: transfer_data(f.read, c.update) return c.hexdigest() finally: f.close()
def clean(self, output_filename=None): """cleans the file of known data and metadata problems output_filename is an optional filename of the fixed file if present, a new AudioFile is written to that path otherwise, only a dry-run is performed and no new file is written return list of fixes performed as Unicode strings raises IOError if unable to write the file or its metadata raises ValueError if the file has errors of some sort """ from audiotools.id3 import total_id3v2_comments from audiotools import transfer_data from audiotools import open as open_audiofile from audiotools.text import CLEAN_REMOVE_DUPLICATE_ID3V2 with open(self.filename, "rb") as f: if total_id3v2_comments(f) > 1: file_fixes = [CLEAN_REMOVE_DUPLICATE_ID3V2] else: file_fixes = [] if output_filename is None: # dry run only metadata = self.get_metadata() if metadata is not None: (metadata, fixes) = metadata.clean() return file_fixes + fixes else: return file_fixes else: # perform complete fix input_f = open(self.filename, "rb") output_f = open(output_filename, "wb") try: transfer_data(input_f.read, output_f.write) finally: input_f.close() output_f.close() new_track = open_audiofile(output_filename) metadata = self.get_metadata() if metadata is not None: (metadata, fixes) = metadata.clean() if len(file_fixes + fixes) > 0: # only update metadata if fixes are actually performed new_track.update_metadata(metadata) return file_fixes + fixes else: return file_fixes
def clean(self, output_filename=None): """cleans the file of known data and metadata problems output_filename is an optional filename of the fixed file if present, a new AudioFile is written to that path otherwise, only a dry-run is performed and no new file is written return list of fixes performed as Unicode strings raises IOError if unable to write the file or its metadata raises ValueError if the file has errors of some sort """ from audiotools.id3 import total_id3v2_comments from audiotools import open as open_audiofile from audiotools import transfer_data from audiotools.text import CLEAN_REMOVE_DUPLICATE_ID3V2 with open(self.filename, "rb") as f: if (total_id3v2_comments(f) > 1): file_fixes = [CLEAN_REMOVE_DUPLICATE_ID3V2] else: file_fixes = [] if (output_filename is None): # dry run only metadata = self.get_metadata() if (metadata is not None): (metadata, fixes) = metadata.clean() return file_fixes + fixes else: return [] else: # perform full fix input_f = open(self.filename, "rb") output_f = open(output_filename, "wb") try: transfer_data(input_f.read, output_f.write) finally: input_f.close() output_f.close() new_track = open_audiofile(output_filename) metadata = self.get_metadata() if (metadata is not None): (metadata, fixes) = metadata.clean() if (len(file_fixes + fixes) > 0): # only update metadata if fixes are actually performed new_track.update_metadata(metadata) return file_fixes + fixes else: return []
def set_metadata(self, metadata): """Takes a MetaData object and sets this track's metadata. This metadata includes track name, album name, and so on. Raises IOError if unable to write the file.""" if (metadata is None): return import tempfile id3_chunk = ID3v22Comment.converted(metadata).build() new_aiff = tempfile.TemporaryFile() new_aiff.seek(12, 0) id3_found = False for (chunk_id, chunk_length, chunk_file) in self.chunk_files(): if (chunk_id != 'ID3 '): new_aiff.write( self.CHUNK_HEADER.build( Con.Container(chunk_id=chunk_id, chunk_length=chunk_length))) transfer_data(chunk_file.read, new_aiff.write) else: new_aiff.write( self.CHUNK_HEADER.build( Con.Container(chunk_id='ID3 ', chunk_length=len(id3_chunk)))) new_aiff.write(id3_chunk) id3_found = True if (not id3_found): new_aiff.write( self.CHUNK_HEADER.build( Con.Container(chunk_id='ID3 ', chunk_length=len(id3_chunk)))) new_aiff.write(id3_chunk) header = Con.Container(aiff_id='FORM', aiff_size=new_aiff.tell() - 8, aiff_type='AIFF') new_aiff.seek(0, 0) new_aiff.write(self.AIFF_HEADER.build(header)) new_aiff.seek(0, 0) f = open(self.filename, 'wb') transfer_data(new_aiff.read, f.write) new_aiff.close() f.close()
def set_metadata(self, metadata): """Takes a MetaData object and sets this track's metadata. This metadata includes track name, album name, and so on. Raises IOError if unable to write the file.""" if (metadata is None): return import tempfile id3_chunk = ID3v22Comment.converted(metadata).build() new_aiff = tempfile.TemporaryFile() new_aiff.seek(12, 0) id3_found = False for (chunk_id, chunk_length, chunk_file) in self.chunk_files(): if (chunk_id != 'ID3 '): new_aiff.write(self.CHUNK_HEADER.build( Con.Container(chunk_id=chunk_id, chunk_length=chunk_length))) transfer_data(chunk_file.read, new_aiff.write) else: new_aiff.write(self.CHUNK_HEADER.build( Con.Container(chunk_id='ID3 ', chunk_length=len(id3_chunk)))) new_aiff.write(id3_chunk) id3_found = True if (not id3_found): new_aiff.write(self.CHUNK_HEADER.build( Con.Container(chunk_id='ID3 ', chunk_length=len(id3_chunk)))) new_aiff.write(id3_chunk) header = Con.Container( aiff_id='FORM', aiff_size=new_aiff.tell() - 8, aiff_type='AIFF') new_aiff.seek(0, 0) new_aiff.write(self.AIFF_HEADER.build(header)) new_aiff.seek(0, 0) f = open(self.filename, 'wb') transfer_data(new_aiff.read, f.write) new_aiff.close() f.close()
def _checksum_track_audiotools( self, track: cd.Track) -> Dict[int, AccurateRipTrackID1]: file = audiotools.open(self._wav) # just doublechecking if not isinstance(file, audiotools.WaveAudio) \ or not file.supports_to_pcm() \ or file.channels() != 2 \ or file.sample_rate() != cd.CDA_SAMLES_PER_SEC \ or file.bits_per_sample() != cd.CDA_BITS_PER_SAMPLE: raise AccurateRipException( "Input file doesn't look like a CDA rip") # most of this is taken from https://github.com/tuffy/python-audio-tools/blob/master/trackverify#L244 reader = file.to_pcm() if not hasattr(reader, "seek") or not callable(reader.seek): raise AccurateRipException("Can't seek in file") # we start reading a bit before the track, in order to try out different offsets for the accuraterip checksums # the reader below will take care of padding if this is negative offset = track.first_sample - self.PREVIOUS_TRACK_FRAMES if offset > 0: offset -= reader.seek(offset) checksummer = audiotools.accuraterip.Checksum( total_pcm_frames=track.length_samples, sample_rate=cd.CDA_SAMLES_PER_SEC, is_first=track.is_first, is_last=track.is_last, pcm_frame_range=self.PREVIOUS_TRACK_FRAMES + 1 + self.NEXT_TRACK_FRAMES, accurateripv2_offset=self.PREVIOUS_TRACK_FRAMES) window_reader = audiotools.PCMReaderWindow( reader, offset, self.PREVIOUS_TRACK_FRAMES + track.length_samples + self.NEXT_TRACK_FRAMES) audiotools.transfer_data(window_reader.read, checksummer.update) checksums_v1 = checksummer.checksums_v1() crc1_by_offset: Dict[int, AccurateRipTrackID1] = { i: AccurateRipTrackID1(c) for i, c in enumerate(checksums_v1, -self.PREVIOUS_TRACK_FRAMES) } return crc1_by_offset
def delete_metadata(self): """Deletes the track's MetaData. Raises IOError if unable to write the file.""" from .bitstream import BitstreamReader, BitstreamWriter f = file(self.filename, "r+b") f.seek(-32, 2) (preamble, version, tag_size, item_count, read_only, item_encoding, is_header, no_footer, has_header) = BitstreamReader(f, 1).parse(ApeTag.HEADER_FORMAT) if ((preamble == 'APETAGEX') and (version == 2000)): #there's existing metadata to delete #so rewrite file without trailing metadata tag if (has_header): old_tag_size = 32 + tag_size else: old_tag_size = tag_size import tempfile from os.path import getsize rewritten = tempfile.TemporaryFile() #copy everything but the last "old_tag_size" bytes #from existing file to rewritten file f = open(self.filename, "rb") limited_transfer_data(f.read, rewritten.write, os.path.getsize(self.filename) - old_tag_size) f.close() #finally, overwrite current file with rewritten file rewritten.seek(0, 0) f = open(self.filename, "wb") transfer_data(rewritten.read, f.write) f.close() rewritten.close()
def __from_wave__(cls, filename, wave_filename, compression=None): if (str(compression) not in cls.COMPRESSION_MODES): compression = cls.DEFAULT_COMPRESSION #mppenc requires files to end with .mpc for some reason if (not filename.endswith(".mpc")): import tempfile actual_filename = filename tempfile = tempfile.NamedTemporaryFile(suffix=".mpc") filename = tempfile.name else: actual_filename = tempfile = None ###Musepack SV7### #sub = subprocess.Popen([BIN['mppenc'], # "--silent", # "--overwrite", # "--%s" % (compression), # wave_filename, # filename], # preexec_fn=ignore_sigint) ###Musepack SV8### sub = subprocess.Popen([BIN['mpcenc'], "--silent", "--overwrite", "--%s" % (compression), wave_filename, filename]) if (sub.wait() == 0): if (tempfile is not None): filename = actual_filename f = file(filename, 'wb') tempfile.seek(0, 0) transfer_data(tempfile.read, f.write) f.close() tempfile.close() return MusepackAudio(filename) else: if (tempfile is not None): tempfile.close() raise EncodingError(u"error encoding file with mpcenc")
def set_metadata(self, metadata): if (metadata is None): return import tempfile id3_chunk = ID3v22Comment.converted(metadata).build() new_aiff = tempfile.TemporaryFile() new_aiff.seek(12, 0) id3_found = False for (chunk_id, chunk_length, chunk_file) in self.chunk_files(): if (chunk_id != 'ID3 '): new_aiff.write( self.CHUNK_HEADER.build( construct.Container(chunk_id=chunk_id, chunk_length=chunk_length))) transfer_data(chunk_file.read, new_aiff.write) else: new_aiff.write( self.CHUNK_HEADER.build( construct.Container(chunk_id='ID3 ', chunk_length=len(id3_chunk)))) new_aiff.write(id3_chunk) id3_found = True if (not id3_found): new_aiff.write( self.CHUNK_HEADER.build( construct.Container(chunk_id='ID3 ', chunk_length=len(id3_chunk)))) new_aiff.write(id3_chunk) header = construct.Container(aiff_id='FORM', aiff_size=new_aiff.tell() - 8, aiff_type='AIFF') new_aiff.seek(0, 0) new_aiff.write(self.AIFF_HEADER.build(header)) new_aiff.seek(0, 0) f = open(self.filename, 'wb') transfer_data(new_aiff.read, f.write) new_aiff.close() f.close()
def update_metadata(self, metadata): """takes this track's current MetaData object as returned by get_metadata() and sets this track's metadata with any fields updated in that object raises IOError if unable to write the file """ import tempfile from .bitstream import BitstreamRecorder if (metadata is None): return elif (not isinstance(metadata, ID3v22Comment)): raise ValueError(_(u"metadata not from audio file")) def chunk_filter(chunks, id3_chunk_data): for chunk in chunks: if (chunk.id == "ID3 "): yield AIFF_Chunk("ID3 ", len(id3_chunk_data), id3_chunk_data) else: yield chunk #turn our ID3v2.2 tag into a raw binary chunk id3_chunk = BitstreamRecorder(0) metadata.build(id3_chunk) id3_chunk = id3_chunk.data() #generate a temporary AIFF file in which our new ID3v2.2 chunk #replaces the existing ID3v2.2 chunk new_aiff = tempfile.NamedTemporaryFile(suffix=self.SUFFIX) self.__class__.aiff_from_chunks(new_aiff.name, chunk_filter(self.chunks(), id3_chunk)) #replace the existing file with data from the temporary file new_file = open(new_aiff.name, 'rb') old_file = open(self.filename, 'wb') transfer_data(new_file.read, old_file.write) old_file.close() new_file.close()
def set_metadata(self, metadata): """takes a MetaData object and sets this track's metadata this metadata includes track name, album name, and so on raises IOError if unable to write the file""" if (metadata is None): return elif (self.get_metadata() is not None): #current file has metadata, so replace it with new metadata self.update_metadata(ID3v22Comment.converted(metadata)) else: #current file has no metadata, so append new ID3 block import tempfile from .bitstream import BitstreamRecorder def chunk_filter(chunks, id3_chunk_data): for chunk in chunks: yield chunk yield AIFF_Chunk("ID3 ", len(id3_chunk_data), id3_chunk_data) #turn our ID3v2.2 tag into a raw binary chunk id3_chunk = BitstreamRecorder(0) ID3v22Comment.converted(metadata).build(id3_chunk) id3_chunk = id3_chunk.data() #generate a temporary AIFF file in which our new ID3v2.2 chunk #is appended to the file's set of chunks new_aiff = tempfile.NamedTemporaryFile(suffix=self.SUFFIX) self.__class__.aiff_from_chunks(new_aiff.name, chunk_filter(self.chunks(), id3_chunk)) #replace the existing file with data from the temporary file new_file = open(new_aiff.name, 'rb') old_file = open(self.filename, 'wb') transfer_data(new_file.read, old_file.write) old_file.close() new_file.close()
def to_wave(self, wave_filename): if (self.filename.endswith(".ape")): devnull = file(os.devnull, "wb") sub = subprocess.Popen( [BIN['mac'], self.filename, wave_filename, '-d'], stdout=devnull, stderr=devnull) sub.wait() devnull.close() else: devnull = file(os.devnull, 'ab') import tempfile ape = tempfile.NamedTemporaryFile(suffix='.ape') f = file(self.filename, 'rb') transfer_data(f.read, ape.write) f.close() ape.flush() sub = subprocess.Popen([BIN['mac'], ape.name, wave_filename, '-d'], stdout=devnull, stderr=devnull) sub.wait() ape.close() devnull.close()
def delete_metadata(self): import tempfile new_aiff = tempfile.TemporaryFile() new_aiff.seek(12, 0) for (chunk_id, chunk_length, chunk_file) in self.chunk_files(): if (chunk_id != 'ID3 '): new_aiff.write( self.CHUNK_HEADER.build( construct.Container(chunk_id=chunk_id, chunk_length=chunk_length))) transfer_data(chunk_file.read, new_aiff.write) header = construct.Container(aiff_id='FORM', aiff_size=new_aiff.tell() - 8, aiff_type='AIFF') new_aiff.seek(0, 0) new_aiff.write(self.AIFF_HEADER.build(header)) new_aiff.seek(0, 0) f = open(self.filename, 'wb') transfer_data(new_aiff.read, f.write) new_aiff.close() f.close()
def update_metadata(self, metadata): """takes this track's current MetaData object as returned by get_metadata() and sets this track's metadata with any fields updated in that object raises IOError if unable to write the file """ import os from audiotools import (TemporaryFile, LimitedFileReader, transfer_data) from audiotools.id3 import (ID3v2Comment, ID3CommentPair) from audiotools.id3v1 import ID3v1Comment from audiotools.bitstream import BitstreamWriter if metadata is None: return elif (not (isinstance(metadata, ID3v2Comment) or isinstance(metadata, ID3CommentPair) or isinstance(metadata, ID3v1Comment))): from audiotools.text import ERR_FOREIGN_METADATA raise ValueError(ERR_FOREIGN_METADATA) elif not os.access(self.filename, os.W_OK): raise IOError(self.filename) new_mp3 = TemporaryFile(self.filename) # get the original MP3 data old_mp3 = open(self.filename, "rb") MP3Audio.__find_last_mp3_frame__(old_mp3) data_end = old_mp3.tell() old_mp3.seek(0, 0) MP3Audio.__find_mp3_start__(old_mp3) data_start = old_mp3.tell() old_mp3 = LimitedFileReader(old_mp3, data_end - data_start) # write id3v2 + data + id3v1 to file if isinstance(metadata, ID3CommentPair): metadata.id3v2.build(BitstreamWriter(new_mp3, False)) transfer_data(old_mp3.read, new_mp3.write) metadata.id3v1.build(new_mp3) elif isinstance(metadata, ID3v2Comment): metadata.build(BitstreamWriter(new_mp3, False)) transfer_data(old_mp3.read, new_mp3.write) elif isinstance(metadata, ID3v1Comment): transfer_data(old_mp3.read, new_mp3.write) metadata.build(new_mp3) # commit change to disk old_mp3.close() new_mp3.close()
def update_metadata(self, metadata): """takes this track's current MetaData object as returned by get_metadata() and sets this track's metadata with any fields updated in that object raises IOError if unable to write the file """ import os from audiotools import (TemporaryFile, LimitedFileReader, transfer_data) from audiotools.id3 import (ID3v2Comment, ID3CommentPair) from audiotools.id3v1 import ID3v1Comment from audiotools.bitstream import BitstreamWriter if (metadata is None): return elif (not (isinstance(metadata, ID3v2Comment) or isinstance(metadata, ID3CommentPair) or isinstance(metadata, ID3v1Comment))): from .text import ERR_FOREIGN_METADATA raise ValueError(ERR_FOREIGN_METADATA) elif (not os.access(self.filename, os.W_OK)): raise IOError(self.filename) new_mp3 = TemporaryFile(self.filename) #get the original MP3 data old_mp3 = open(self.filename, "rb") MP3Audio.__find_last_mp3_frame__(old_mp3) data_end = old_mp3.tell() old_mp3.seek(0, 0) MP3Audio.__find_mp3_start__(old_mp3) data_start = old_mp3.tell() old_mp3 = LimitedFileReader(old_mp3, data_end - data_start) #write id3v2 + data + id3v1 to file if (isinstance(metadata, ID3CommentPair)): metadata.id3v2.build(BitstreamWriter(new_mp3, 0)) transfer_data(old_mp3.read, new_mp3.write) metadata.id3v1.build(new_mp3) elif (isinstance(metadata, ID3v2Comment)): metadata.build(BitstreamWriter(new_mp3, 0)) transfer_data(old_mp3.read, new_mp3.write) elif (isinstance(metadata, ID3v1Comment)): transfer_data(old_mp3.read, new_mp3.write) metadata.build(new_mp3) #commit change to disk old_mp3.close() new_mp3.close()
def update_and_backup(track, undo_db, messenger): changes_made = False #copy the track to a temporary location temp_track_f = tempfile.NamedTemporaryFile(suffix="." + track.SUFFIX) f = open(track.filename, 'rb') audiotools.transfer_data(f.read, temp_track_f.write) f.close() temp_track_f.flush() temp_track = audiotools.open(temp_track_f.name) #perform any Track fixes on the temporary Track (temp_track, messages) = fix_track(temp_track, dry_run=False) changes_made = changes_made or (len(messages) > 0) display_messages(messenger, track, messages) #perform any MetaData fixes on the temporary Track (metadata, messages) = fix_metadata(temp_track.get_metadata()) changes_made = changes_made or (len(messages) > 0) display_messages(messenger, track, messages) if (len(messages) > 0): temp_track.set_metadata(metadata) if (changes_made): #perform xdelta between the old and new track delta_f = tempfile.NamedTemporaryFile(suffix=".delta") if (subprocess.call([ audiotools.BIN["xdelta"], "delta", temp_track.filename, track.filename, delta_f.name ]) != 2): #store the xdelta in our undo DB f = open(delta_f.name, 'rb') data = cStringIO.StringIO() audiotools.transfer_data(f.read, data.write) f.close() delta_f.close() undo_db[checksum(temp_track_f.name)] = data.getvalue() #copy the temporary track over the original f1 = open(temp_track_f.name, 'rb') f2 = open(track.filename, 'wb') audiotools.transfer_data(f1.read, f2.write) f1.close() f2.close() temp_track_f.close()
cls.__unlink__(filename) raise EncodingError(str(err)) #write header to disk write_header(writer, pcmreader.channels, pcmreader.bits_per_sample, pcmreader.sample_rate, counter.frames_written) #write seektable to disk write_seektable(writer, frame_sizes) #transfer TTA frames from temporary space to disk frames.seek(0, 0) transfer_data(frames.read, file.write) frames.close() file.close() return cls(filename) def data_size(self): """returns the size of the file's data, in bytes, calculated from its header and seektable""" return (22 + # header size (len(self.__frame_lengths__) * 4) + 4 + # seektable size sum(self.__frame_lengths__)) # frames size @classmethod
def accuraterip_image_checksum(progress, track, is_first, is_last, ar_matches, displayed_filename, pcm_frames_offset, total_pcm_frames): from audiotools import (transfer_data, PCMReaderProgress, PCMReaderWindow) from audiotools.accuraterip import Checksum, match_offset reader = track.to_pcm() pcm_frames_offset -= PREVIOUS_TRACK_FRAMES # if PCMReader has seek(), use it to reduce the amount of frames to skip if (hasattr(reader, "seek") and callable(reader.seek) and (pcm_frames_offset > 0)): pcm_frames_offset -= reader.seek(pcm_frames_offset) # feed stream to checksummers checksummer = Checksum(total_pcm_frames=total_pcm_frames, sample_rate=track.sample_rate(), is_first=is_first, is_last=is_last, pcm_frame_range=PREVIOUS_TRACK_FRAMES + 1 + NEXT_TRACK_FRAMES, accurateripv2_offset=PREVIOUS_TRACK_FRAMES) try: pcmreader = audiotools.PCMReaderProgress( audiotools.PCMReaderWindow( reader, pcm_frames_offset, PREVIOUS_TRACK_FRAMES + total_pcm_frames + NEXT_TRACK_FRAMES), PREVIOUS_TRACK_FRAMES + total_pcm_frames + NEXT_TRACK_FRAMES, progress) audiotools.transfer_data(pcmreader.read, checksummer.update) except (IOError, ValueError) as err: return { "filename": displayed_filename, "error": str(err), "v1": { "checksum": None, "offset": None, "confidence": None }, "v2": { "checksum": None, "offset": None, "confidence": None } } # determine checksum, confidence and offset from # the calculated checksums and possible AccurateRip matches (checksum_v2, confidence_v2, offset_v2) = match_offset(ar_matches=ar_matches, checksums=[checksummer.checksum_v2()], initial_offset=0) (checksum_v1, confidence_v1, offset_v1) = match_offset(ar_matches=ar_matches, checksums=checksummer.checksums_v1(), initial_offset=-PREVIOUS_TRACK_FRAMES) if len(ar_matches) == 0: return { "filename": displayed_filename, "error": None, "v1": { "checksum": checksum_v1, "offset": offset_v1, "confidence": AR_NOT_FOUND }, "v2": { "checksum": checksum_v2, "offset": offset_v2, "confidence": AR_NOT_FOUND } } else: return { "filename": displayed_filename, "error": None, "v1": { "checksum": checksum_v1, "offset": offset_v1, "confidence": (confidence_v1 if (confidence_v1 is not None) else AR_MISMATCH) }, "v2": { "checksum": checksum_v2, "offset": offset_v2, "confidence": (confidence_v2 if (confidence_v2 is not None) else AR_MISMATCH) } }
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=None): """encodes a new file from PCM data takes a filename string, PCMReader object, optional compression level string and optional total_pcm_frames integer encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new M4AAudio object""" import subprocess import os from audiotools import PCMConverter from audiotools import transfer_data from audiotools import transfer_framelist_data from audiotools import ignore_sigint from audiotools import EncodingError from audiotools import DecodingError from audiotools import ChannelMask from audiotools import __default_quality__ if ((compression is None) or (compression not in cls.COMPRESSION_MODES)): compression = __default_quality__(cls.NAME) if pcmreader.bits_per_sample not in {8, 16, 24}: from audiotools import UnsupportedBitsPerSample pcmreader.close() raise UnsupportedBitsPerSample(filename, pcmreader.bits_per_sample) if pcmreader.channels > 2: pcmreader = PCMConverter(pcmreader, sample_rate=pcmreader.sample_rate, channels=2, channel_mask=ChannelMask.from_channels(2), bits_per_sample=pcmreader.bits_per_sample) # faac requires files to end with .m4a for some reason if not filename.endswith(".m4a"): import tempfile actual_filename = filename tempfile = tempfile.NamedTemporaryFile(suffix=".m4a") filename = tempfile.name else: actual_filename = tempfile = None sub = subprocess.Popen( [BIN['faac'], "-q", compression, "-P", "-R", str(pcmreader.sample_rate), "-B", str(pcmreader.bits_per_sample), "-C", str(pcmreader.channels), "-X", "-o", filename, "-"], stdin=subprocess.PIPE, stderr=subprocess.DEVNULL if hasattr(subprocess, "DEVNULL") else open(os.devnull, "wb"), stdout=subprocess.DEVNULL if hasattr(subprocess, "DEVNULL") else open(os.devnull, "wb"), preexec_fn=ignore_sigint) # Note: faac handles SIGINT on its own, # so trying to ignore it doesn't work like on most other encoders. try: if total_pcm_frames is not None: from audiotools import CounterPCMReader pcmreader = CounterPCMReader(pcmreader) transfer_framelist_data(pcmreader, sub.stdin.write) if ((total_pcm_frames is not None) and (total_pcm_frames != pcmreader.frames_written)): from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH) except (ValueError, IOError) as err: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise EncodingError(str(err)) except Exception: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise sub.stdin.close() if sub.wait() == 0: if tempfile is not None: filename = actual_filename f = open(filename, 'wb') tempfile.seek(0, 0) transfer_data(tempfile.read, f.write) f.close() tempfile.close() return M4AAudio(filename) else: if tempfile is not None: tempfile.close() raise EncodingError(u"unable to write file with faac")
"""Writes the contents of this file to the given .wav filename string. Raises EncodingError if some error occurs during decoding.""" try: self.verify() except InvalidWave, err: raise EncodingError(str(err)) try: output = file(wave_filename, 'wb') input = file(self.filename, 'rb') except IOError, msg: raise EncodingError(str(msg)) try: transfer_data(input.read, output.write) finally: input.close() output.close() @classmethod def from_wave(cls, filename, wave_filename, compression=None, progress=None): """Encodes a new AudioFile from an existing .wav file. Takes a filename string, wave_filename string of an existing WaveAudio file and an optional compression level string. Encodes a new audio file from the wave's data at the given filename with the specified compression level and returns a new WaveAudio object."""
def rip_disk(self): """Most of this is just copied from cdda2track, a part of python-audio-tools""" try: cddareader = CDDAReader(app.config['ripper']['cdrom'], True) track_offsets = cddareader.track_offsets track_lengths = cddareader.track_lengths except (IOError, ValueError, OSError) as err: self.update_state(state='FAILURE', meta={'error': str(err)}) raise Ignore() if "offset" in app.config['ripper']: read_offset = app.config['ripper']['offset'] else: read_offset = 0 if "speed" in app.config['ripper']: cddareader.set_speed(app.config['ripper']['speed']) pre_gap_length = cddareader.track_offsets[1] if pre_gap_length > 0: with audiotools.BufferedPCMReader(audiotools.PCMReaderWindow(cddareader, read_offset, pre_gap_length, forward_close=False)) as r: preserve_pre_gap = set(r.read(pre_gap_length)) != {0} if preserve_pre_gap: track_offsets[0] = 0 track_lengths[0] = pre_gap_length else: preserve_pre_gap = False self.update_state(state='PROGRESS', meta={'current': None, 'total': None, 'album': None, 'artist': None, 'metadata': [], 'status': 'Reading metadata'}) metadata_choices = audiotools.cddareader_metadata_lookup(cddareader) if preserve_pre_gap: # prepend "track 0" track to start of list for each choice for choice in metadata_choices: track_0 = merge_metadatas(choice) track_0.track_number = 0 choice.insert(0, track_0) album = metadata_choices[0][0].album_name artist = metadata_choices[0][0].artist_name self.update_state(state='PROGRESS', meta={'current': None, 'total': len(track_offsets), 'album': album, 'artist': artist, 'metadata': jsonify_metadata(metadata_choices[0]), 'status': 'Got metadata, preparing to rip'}) tracks_to_rip = list(sorted(track_offsets.keys())) try: output_tracks = list( process_output_options( metadata_choices=[ [c for i, c in enumerate(choices, 0 if preserve_pre_gap else 1) if i in tracks_to_rip] for choices in metadata_choices], input_filenames=[ audiotools.Filename("track{:02d}.cdda.wav".format(i)) for i in tracks_to_rip], output_directory=app.config['ripper']['output_directory'], format_string=None, output_class=audiotools.wav.WaveAudio, quality='', msg=msg, use_default=True)) except audiotools.UnsupportedTracknameField as err: self.update_state(state='FAILURE', meta={'error': str(err)}) raise Ignore() encoded = [] rip_log = {} accuraterip_log_v1 = {} accuraterip_log_v2 = {} replay_gain = audiotools.ReplayGainCalculator(cddareader.sample_rate) for (track_number, index, (output_class, output_filename, output_quality, output_metadata)) in zip(tracks_to_rip, range(1, len(tracks_to_rip) + 1), output_tracks): self.update_state(state='PROGRESS', meta={'current': track_number, 'total': len(tracks_to_rip), 'album': album, 'artist': artist, 'metadata': jsonify_metadata(metadata_choices[0]), 'status': 'Preparing to rip'}) cddareader.reset_log() track_offset = (track_offsets[track_number] + read_offset - PREVIOUS_TRACK_FRAMES) track_length = track_lengths[track_number] # seek to indicated starting offset if track_offset > 0: seeked_offset = cddareader.seek(track_offset) else: seeked_offset = cddareader.seek(0) # make leading directories, if necessary try: audiotools.make_dirs(str(output_filename)) except OSError as err: self.update_state(state='FAILURE', meta={'error': str(err)}) raise Ignore() # setup individual progress bar per track state = {'current': track_number, 'total': len(tracks_to_rip), 'album': album, 'artist': artist, 'metadata': jsonify_metadata(metadata_choices[0]), 'status': 'Ripping...'} progress = CeleryProgressDisplay(msg, state, self) # perform extraction over an AccurateRip window track_data = audiotools.PCMReaderWindow( cddareader, track_offset - seeked_offset, PREVIOUS_TRACK_FRAMES + track_length + NEXT_TRACK_FRAMES) # with AccurateRip calculated during extraction accuraterip = AccurateRipReader( track_data, track_length, track_number == min(track_offsets.keys()), track_number == max(track_offsets.keys())) try: # encode output file itself track = output_class.from_pcm( str(output_filename), replay_gain.to_pcm( audiotools.PCMReaderProgress( audiotools.PCMReaderWindow( accuraterip, PREVIOUS_TRACK_FRAMES, track_length, forward_close=False), track_length, progress.update)), output_quality, total_pcm_frames=track_length) encoded.append(track) # since the inner PCMReaderWindow only outputs part # of the accuraterip reader, we need to ensure # anything left over in accuraterip gets processed also audiotools.transfer_data(accuraterip.read, lambda f: None) except audiotools.EncodingError as err: self.update_state(state='FAILURE', meta={'error': str(err)}) raise Ignore() track.set_metadata(output_metadata) rip_log[track_number] = cddareader.log() accuraterip_log_v1[track_number] = accuraterip.checksums_v1() accuraterip_log_v2[track_number] = accuraterip.checksums_v2() self.update_state(state='SUCCESS', meta={'total': len(tracks_to_rip), 'album': album, 'artist': artist, 'metadata': jsonify_metadata(metadata_choices[0]), 'status': 'Fully ripped'}) return {'status': 'done', 'tracks': len(tracks_to_rip)}
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=None, encoding_function=None): """encodes a new file from PCM data takes a filename string, PCMReader object, optional compression level string and optional total_pcm_frames integer encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new AudioFile-compatible object may raise EncodingError if some problem occurs when encoding the input file. This includes an error in the input stream, a problem writing the output file, or even an EncodingError subclass such as "UnsupportedBitsPerSample" if the input stream is formatted in a way this class is unable to support """ from audiotools import (BufferedPCMReader, CounterPCMReader, transfer_data, EncodingError) # from audiotools.py_encoders import encode_tta from audiotools.encoders import encode_tta from audiotools.bitstream import BitstreamWriter # open output file right away # so we can fail as soon as possible try: file = open(filename, "wb") except IOError as err: pcmreader.close() raise EncodingError(str(err)) writer = BitstreamWriter(file, True) counter = CounterPCMReader(pcmreader) try: if (total_pcm_frames is not None): # write header to disk write_header(writer, pcmreader.channels, pcmreader.bits_per_sample, pcmreader.sample_rate, total_pcm_frames) block_size = (pcmreader.sample_rate * 256) // 245 total_tta_frames = div_ceil(total_pcm_frames, block_size) # write temporary seektable to disk writer.mark() write_seektable(writer, [0] * total_tta_frames) writer.flush() # write frames to disk try: frame_sizes = \ (encode_tta if encoding_function is None else encoding_function)(file, BufferedPCMReader(counter)) except (IOError, ValueError) as err: cls.__unlink__(filename) raise EncodingError(str(err)) # ensure written number of PCM frames # matches total_pcm_frames if (counter.frames_written != total_pcm_frames): from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH cls.__unlink__(filename) raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH) assert (len(frame_sizes) == total_tta_frames) # go back and rewrite seektable with completed one writer.rewind() write_seektable(writer, frame_sizes) writer.unmark() else: import tempfile frames = tempfile.TemporaryFile() # encode TTA frames to temporary file try: frame_sizes = \ (encode_tta if encoding_function is None else encoding_function)(frames, BufferedPCMReader(counter)) except (IOError, ValueError) as err: frames.close() cls.__unlink__(filename) raise EncodingError(str(err)) # write header to disk write_header(writer, pcmreader.channels, pcmreader.bits_per_sample, pcmreader.sample_rate, counter.frames_written) # write seektable to disk write_seektable(writer, frame_sizes) # transfer TTA frames from temporary space to disk frames.seek(0, 0) transfer_data(frames.read, writer.write_bytes) frames.close() finally: counter.close() if (writer.has_mark()): writer.unmark() writer.close() return cls(filename)
sub.wait() cls.__unlink__(filename) raise err try: pcmreader.close() except DecodingError, err: raise EncodingError(err.error_message) sub.stdin.close() if sub.wait() == 0: if tempfile is not None: filename = actual_filename f = file(filename, "wb") tempfile.seek(0, 0) transfer_data(tempfile.read, f.write) f.close() tempfile.close() return M4AAudio(filename) else: if tempfile is not None: tempfile.close() raise EncodingError(u"unable to write file with faac") @classmethod def can_add_replay_gain(cls): """Returns False.""" return False
def update_metadata(self, metadata): """takes this track's current MetaData object as returned by get_metadata() and sets this track's metadata with any fields updated in that object raises IOError if unable to write the file """ import os from audiotools.ape import ApeTag from audiotools.id3 import ID3v2Comment from audiotools.id3 import ID3CommentPair from audiotools.id3v1 import ID3v1Comment if (metadata is None): return elif (not os.access(self.filename, os.W_OK)): raise IOError(self.filename) # ensure metadata is APEv2, ID3v2, ID3v1, or ID3CommentPair if (((not isinstance(metadata, ApeTag)) and (not isinstance(metadata, ID3v2Comment)) and (not isinstance(metadata, ID3CommentPair)) and (not isinstance(metadata, ID3v1Comment)))): from audiotools.text import ERR_FOREIGN_METADATA raise ValueError(ERR_FOREIGN_METADATA) current_metadata = self.get_metadata() if (isinstance(metadata, ApeTag) and (current_metadata is None)): # if new metadata is APEv2 and no current metadata, # simply append APEv2 tag from audiotools.bitstream import BitstreamWriter with BitstreamWriter(open(self.filename, "ab"), True) as writer: metadata.build(writer) elif (isinstance(metadata, ApeTag) and isinstance(current_metadata, ApeTag) and (metadata.total_size() > current_metadata.total_size())): # if new metadata is APEv2, current metadata is APEv2 # and new metadata is larger, # overwrite old tag with new tag from audiotools.bitstream import BitstreamWriter with open(self.filename, "r+b") as f: f.seek(-current_metadata.total_size(), 2) metadata.build(BitstreamWriter(f, True)) else: from audiotools.bitstream import BitstreamWriter from audiotools import (transfer_data, LimitedFileReader, TemporaryFile) from audiotools.id3 import skip_id3v2_comment # otherwise, rebuild TTA with APEv2/ID3 tags in place old_tta = open(self.filename, "rb") skip_id3v2_comment(old_tta) old_tta = LimitedFileReader(old_tta, self.data_size()) new_tta = TemporaryFile(self.filename) if (isinstance(metadata, ApeTag)): transfer_data(old_tta.read, new_tta.write) metadata.build(BitstreamWriter(new_tta, True)) elif (isinstance(metadata, ID3CommentPair)): metadata.id3v2.build(BitstreamWriter(new_tta, False)) transfer_data(old_tta.read, new_tta.write) metadata.id3v1.build(new_tta) elif (isinstance(metadata, ID3v2Comment)): metadata.build(BitstreamWriter(new_tta, False)) transfer_data(old_tta.read, new_tta.write) else: # ID3v1Comment transfer_data(old_tta.read, new_tta.write) metadata.build(new_tta) old_tta.close() new_tta.close()
def accuraterip_checksum(progress, track, previous_track, next_track, ar_matches): from audiotools import (transfer_data, PCMCat, PCMReaderHead, PCMReaderDeHead, PCMReaderProgress) from audiotools.decoders import SameSample from audiotools.accuraterip import Checksum, match_offset # unify previous track, current track and next track into a single stream pcmreaders = [] if previous_track is not None: frames_to_skip = previous_track.total_frames() - PREVIOUS_TRACK_FRAMES prev_pcmreader = previous_track.to_pcm() if hasattr(prev_pcmreader, "seek") and callable(prev_pcmreader.seek): frames_to_skip -= prev_pcmreader.seek(frames_to_skip) pcmreaders.append(PCMReaderDeHead(prev_pcmreader, frames_to_skip)) else: pcmreaders.append( SameSample(sample=0, total_pcm_frames=PREVIOUS_TRACK_FRAMES, sample_rate=track.sample_rate(), channels=track.channels(), channel_mask=int(track.channel_mask()), bits_per_sample=track.bits_per_sample())) pcmreaders.append(track.to_pcm()) if next_track is not None: pcmreaders.append(PCMReaderHead(next_track.to_pcm(), NEXT_TRACK_FRAMES)) else: pcmreaders.append( SameSample(sample=0, total_pcm_frames=NEXT_TRACK_FRAMES, sample_rate=track.sample_rate(), channels=track.channels(), channel_mask=int(track.channel_mask()), bits_per_sample=track.bits_per_sample())) # feed stream to checksummer checksummer = Checksum(total_pcm_frames=track.total_frames(), sample_rate=track.sample_rate(), is_first=(previous_track is None), is_last=(next_track is None), pcm_frame_range=PREVIOUS_TRACK_FRAMES + 1 + NEXT_TRACK_FRAMES, accurateripv2_offset=PREVIOUS_TRACK_FRAMES) try: pcmreader = PCMReaderProgress( PCMCat(pcmreaders), PREVIOUS_TRACK_FRAMES + track.total_frames() + NEXT_TRACK_FRAMES, progress) audiotools.transfer_data(pcmreader.read, checksummer.update) except (IOError, ValueError) as err: return { "filename": audiotools.Filename(track.filename).__unicode__(), "error": str(err), "v1": { "checksum": None, "offset": None, "confidence": None }, "v2": { "checksum": None, "offset": None, "confidence": None } } # determine checksum, confidence and offset from # the calculated checksums and possible AccurateRip matches (checksum_v2, confidence_v2, offset_v2) = match_offset(ar_matches, checksums=[checksummer.checksum_v2()], initial_offset=0) (checksum_v1, confidence_v1, offset_v1) = match_offset(ar_matches=ar_matches, checksums=checksummer.checksums_v1(), initial_offset=-PREVIOUS_TRACK_FRAMES) if len(ar_matches) == 0: return { "filename": audiotools.Filename(track.filename).__unicode__(), "error": None, "v1": { "checksum": checksum_v1, "offset": offset_v1, "confidence": AR_NOT_FOUND }, "v2": { "checksum": checksum_v2, "offset": offset_v2, "confidence": AR_NOT_FOUND } } else: return { "filename": audiotools.Filename(track.filename).__unicode__(), "error": None, "v1": { "checksum": checksum_v1, "offset": offset_v1, "confidence": (confidence_v1 if (confidence_v1 is not None) else AR_MISMATCH) }, "v2": { "checksum": checksum_v2, "offset": offset_v2, "confidence": (confidence_v2 if (confidence_v2 is not None) else AR_MISMATCH) } }
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=None, encoding_function=None): """encodes a new file from PCM data takes a filename string, PCMReader object, optional compression level string and optional total_pcm_frames integer encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new AudioFile-compatible object may raise EncodingError if some problem occurs when encoding the input file. This includes an error in the input stream, a problem writing the output file, or even an EncodingError subclass such as "UnsupportedBitsPerSample" if the input stream is formatted in a way this class is unable to support """ from audiotools import (BufferedPCMReader, CounterPCMReader, transfer_data, EncodingError) # from audiotools.py_encoders import encode_tta from audiotools.encoders import encode_tta from audiotools.bitstream import BitstreamWriter # open output file right away # so we can fail as soon as possible try: file = open(filename, "wb") except IOError as err: pcmreader.close() raise EncodingError(str(err)) writer = BitstreamWriter(file, True) counter = CounterPCMReader(pcmreader) try: if (total_pcm_frames is not None): # write header to disk write_header(writer, pcmreader.channels, pcmreader.bits_per_sample, pcmreader.sample_rate, total_pcm_frames) block_size = (pcmreader.sample_rate * 256) // 245 total_tta_frames = div_ceil(total_pcm_frames, block_size) # write temporary seektable to disk writer.mark() write_seektable(writer, [0] * total_tta_frames) writer.flush() # write frames to disk try: frame_sizes = \ (encode_tta if encoding_function is None else encoding_function)(file, BufferedPCMReader(counter)) except (IOError, ValueError) as err: cls.__unlink__(filename) raise EncodingError(str(err)) # ensure written number of PCM frames # matches total_pcm_frames if (counter.frames_written != total_pcm_frames): from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH cls.__unlink__(filename) raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH) assert(len(frame_sizes) == total_tta_frames) # go back and rewrite seektable with completed one writer.rewind() write_seektable(writer, frame_sizes) writer.unmark() else: import tempfile frames = tempfile.TemporaryFile() # encode TTA frames to temporary file try: frame_sizes = \ (encode_tta if encoding_function is None else encoding_function)(frames, BufferedPCMReader(counter)) except (IOError, ValueError) as err: frames.close() cls.__unlink__(filename) raise EncodingError(str(err)) # write header to disk write_header(writer, pcmreader.channels, pcmreader.bits_per_sample, pcmreader.sample_rate, counter.frames_written) # write seektable to disk write_seektable(writer, frame_sizes) # transfer TTA frames from temporary space to disk frames.seek(0, 0) transfer_data(frames.read, writer.write_bytes) frames.close() finally: counter.close() if (writer.has_mark()): writer.unmark() writer.close() return cls(filename)
def add_replay_gain(cls, filenames, progress=None): """Adds ReplayGain values to a list of filename strings. All the filenames must be of this AudioFile type. Raises ValueError if some problem occurs during ReplayGain application. """ from audiotools.replaygain import ReplayGain, ReplayGainReader import tempfile wave_files = [track for track in open_files(filenames) if isinstance(track, cls)] track_gains = [] total_frames = sum([track.total_frames() for track in wave_files]) * 2 processed_frames = 0 #first, calculate the Gain and Peak values from our files for original_wave in wave_files: try: rg = ReplayGain(original_wave.sample_rate()) except ValueError: track_gains.append((None, None)) pcm = original_wave.to_pcm() try: try: frame = pcm.read(BUFFER_SIZE) while (len(frame) > 0): processed_frames += frame.frames if (progress is not None): progress(processed_frames, total_frames) rg.update(frame) frame = pcm.read(BUFFER_SIZE) track_gains.append(rg.title_gain()) except ValueError: track_gains.append((None, None)) finally: pcm.close() #then, apply those Gain and Peak values to our files #rewriting the originals in the process for (original_wave, (gain, peak)) in zip(wave_files, track_gains): if (gain is None): continue temp_wav_file = tempfile.NamedTemporaryFile(suffix=".wav") try: (header, footer) = original_wave.pcm_split() temp_wav_file.write(header) replaygain_pcm = ReplayGainReader(original_wave.to_pcm(), gain, peak) frame = replaygain_pcm.read(BUFFER_SIZE) while (len(frame) > 0): processed_frames += frame.frames if (progress is not None): progress(processed_frames, total_frames) temp_wav_file.write(frame.to_bytes( False, original_wave.bits_per_sample() > 8)) frame = replaygain_pcm.read(BUFFER_SIZE) temp_wav_file.write(footer) temp_wav_file.seek(0, 0) new_wave = open(original_wave.filename, 'wb') transfer_data(temp_wav_file.read, new_wave.write) new_wave.close() finally: temp_wav_file.close()
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=None): """encodes a new file from PCM data takes a filename string, PCMReader object, optional compression level string and optional total_pcm_frames integer encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new M4AAudio object""" import subprocess import os from audiotools import PCMConverter from audiotools import transfer_data from audiotools import transfer_framelist_data from audiotools import ignore_sigint from audiotools import EncodingError from audiotools import DecodingError from audiotools import ChannelMask from audiotools import __default_quality__ if ((compression is None) or (compression not in cls.COMPRESSION_MODES)): compression = __default_quality__(cls.NAME) if pcmreader.channels > 2: pcmreader = PCMConverter(pcmreader, sample_rate=pcmreader.sample_rate, channels=2, channel_mask=ChannelMask.from_channels(2), bits_per_sample=pcmreader.bits_per_sample) # faac requires files to end with .m4a for some reason if not filename.endswith(".m4a"): import tempfile actual_filename = filename tempfile = tempfile.NamedTemporaryFile(suffix=".m4a") filename = tempfile.name else: actual_filename = tempfile = None sub = subprocess.Popen( [ BIN['faac'], "-q", compression, "-P", "-R", str(pcmreader.sample_rate), "-B", str(pcmreader.bits_per_sample), "-C", str(pcmreader.channels), "-X", "-o", filename, "-" ], stdin=subprocess.PIPE, stderr=subprocess.DEVNULL if hasattr(subprocess, "DEVNULL") else open(os.devnull, "wb"), stdout=subprocess.DEVNULL if hasattr(subprocess, "DEVNULL") else open(os.devnull, "wb"), preexec_fn=ignore_sigint) # Note: faac handles SIGINT on its own, # so trying to ignore it doesn't work like on most other encoders. try: if total_pcm_frames is not None: from audiotools import CounterPCMReader pcmreader = CounterPCMReader(pcmreader) transfer_framelist_data(pcmreader, sub.stdin.write) if ((total_pcm_frames is not None) and (total_pcm_frames != pcmreader.frames_written)): from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH) except (ValueError, IOError) as err: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise EncodingError(str(err)) except Exception: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise sub.stdin.close() if sub.wait() == 0: if tempfile is not None: filename = actual_filename f = open(filename, 'wb') tempfile.seek(0, 0) transfer_data(tempfile.read, f.write) f.close() tempfile.close() return M4AAudio(filename) else: if tempfile is not None: tempfile.close() raise EncodingError(u"unable to write file with faac")
def update_metadata(self, metadata): if (metadata is None): return elif (not isinstance(metadata, ApeTag)): raise ValueError(_(u"metadata not from audio file")) from .bitstream import BitstreamReader, BitstreamWriter f = file(self.filename, "r+b") f.seek(-32, 2) (preamble, version, tag_size, item_count, read_only, item_encoding, is_header, no_footer, has_header) = BitstreamReader(f, 1).parse(ApeTag.HEADER_FORMAT) if ((preamble == 'APETAGEX') and (version == 2000)): if (has_header): old_tag_size = 32 + tag_size else: old_tag_size = tag_size if (metadata.total_size() >= old_tag_size): #metadata has grown #so append it to existing file f.seek(-old_tag_size, 2) metadata.build(BitstreamWriter(f, 1)) else: #metadata has shrunk #so rewrite file with smaller metadata import tempfile from os.path import getsize rewritten = tempfile.TemporaryFile() #copy everything but the last "old_tag_size" bytes #from existing file to rewritten file f = open(self.filename, "rb") limited_transfer_data(f.read, rewritten.write, os.path.getsize(self.filename) - old_tag_size) f.close() #append new tag to rewritten file metadata.build(BitstreamWriter(rewritten, 1)) #finally, overwrite current file with rewritten file rewritten.seek(0, 0) f = open(self.filename, "wb") transfer_data(rewritten.read, f.write) f.close() rewritten.close() else: #no existing metadata, so simply append a fresh tag f = file(self.filename, "ab") metadata.build(BitstreamWriter(f, 1)) f.close()
def update_metadata(self, metadata): """Takes this track's current MetaData object as returned by get_metadata() and sets this track's metadata with any fields updated in that object. Raises IOError if unable to write the file. """ from struct import pack if (metadata is None): return elif (not isinstance(metadata, ID3v22Comment)): raise _(u"metadata not from audio file") def chunk_filter(chunks, id3_chunk): id3_found = False for (chunk_id, chunk_size, chunk_data) in chunks: if (chunk_id == 'ID3 '): if (len(id3_chunk) % 2): yield (chunk_id, pack(">I", len(id3_chunk)), id3_chunk + chr(0)) else: yield (chunk_id, pack(">I", len(id3_chunk)), id3_chunk) id3_found = True else: yield (chunk_id, chunk_size, chunk_data) else: if (not id3_found): if (len(id3_chunk) % 2): yield ('ID3 ', pack(">I", len(id3_chunk)), id3_chunk + chr(0)) else: yield ('ID3 ', pack(">I", len(id3_chunk)), id3_chunk) import tempfile from .bitstream import BitstreamRecorder #turn our ID3v2.2 tag into a raw binary chunk id3_chunk = BitstreamRecorder(0) metadata.build(id3_chunk) id3_chunk = id3_chunk.data() #generate a temporary AIFF file in which our new ID3v2.2 chunk #replaces the existing ID3v2.2 chunk new_aiff = tempfile.NamedTemporaryFile(suffix=self.SUFFIX) self.__class__.aiff_from_chunks(new_aiff.name, chunk_filter(self.chunks(), id3_chunk)) #replace the existing file with data from the temporary file new_file = open(new_aiff.name, 'rb') old_file = open(self.filename, 'wb') transfer_data(new_file.read, old_file.write) old_file.close() new_file.close()
def to_pcm(self): """Returns a PCMReader object containing the track's PCM data.""" #if mpg123 is available, use that for decoding if (BIN.can_execute(BIN["mpg123"])): sub = subprocess.Popen([BIN["mpg123"], "-qs", self.filename], stdout=subprocess.PIPE, stderr=file(os.devnull, "a")) return PCMReader(sub.stdout, sample_rate=self.sample_rate(), channels=self.channels(), bits_per_sample=16, channel_mask=int(ChannelMask.from_channels( self.channels())), process=sub, big_endian=BIG_ENDIAN) else: #if not, use LAME for decoding if (self.filename.endswith("." + self.SUFFIX)): if (BIG_ENDIAN): endian = ['-x'] else: endian = [] sub = subprocess.Popen([BIN['lame']] + endian + \ ["--decode", "-t", "--quiet", self.filename, "-"], stdout=subprocess.PIPE) return PCMReader( sub.stdout, sample_rate=self.sample_rate(), channels=self.channels(), bits_per_sample=16, channel_mask=int(self.channel_mask()), process=sub) else: import tempfile from audiotools import TempWaveReader #copy our file to one that ends with .mp3 tempmp3 = tempfile.NamedTemporaryFile(suffix='.' + self.SUFFIX) f = open(self.filename, 'rb') transfer_data(f.read, tempmp3.write) f.close() tempmp3.flush() #decode the mp3 file to a WAVE file wave = tempfile.NamedTemporaryFile(suffix='.wav') returnval = subprocess.call([BIN['lame'], "--decode", "--quiet", tempmp3.name, wave.name]) tempmp3.close() if (returnval == 0): #return WAVE file as a stream wave.seek(0, 0) return TempWaveReader(wave) else: return PCMReaderError( error_message=u"lame exited with error", sample_rate=self.sample_rate(), channels=self.channels(), channel_mask=int(self.channel_mask()), bits_per_sample=16)