def run(self): for oqueue, settings in zip(self.oqueues, self.settings): e = Lame(oqueue=oqueue, **settings) self.encoders.append(e) e.start() try: self.ctime = None for i, actions in enumerate(self.loop()): log.info("Rendering audio data for %d actions.", len(actions)) for a in actions: try: with Timer() as t: # TODO: Move the "multiple encoding" support into # LAME itself - it should be able to multiplex the # streams itself. self.encoders[0].add_pcm(a) self.infoqueue.put(generate_metadata(a)) log.info("Rendered in %fs!", t.ms) except: log.error("Could not render %s. Skipping.\n%s", a, traceback.format_exc()) gc.collect() except: log.error("Something failed in mixer.run:\n%s", traceback.format_exc()) self.stop() return
def run(self): database.reset_played() self.encoder = Lame(oqueue=self.oqueue) self.encoder.start() try: self.ctime = None for actions in self.generate_tracks(): log.info("Rendering audio data for %d actions.", len(actions)) for a in actions: try: with Timer() as t: # TODO: Move the "multiple encoding" support into # LAME itself - it should be able to multiplex the # streams itself. self.encoder.add_pcm(a) if self.infoqueue: self.infoqueue.put(generate_metadata(a)) log.info("Rendered in %fs!", t.ms) except Exception: log.error("Could not render %s. Skipping.\n%s SEE???", a, traceback.format_exc()) gc.collect() except Exception: log.error("Something failed in mixer.run:\n%s", traceback.format_exc()) self.stop() return
def audition_render(actions, filename): """Calls render on each action in actions, concatenates the results, and renders an audio file""" print("Calling render()!") print(actions) print(filename) encoder = Lame(ofile=open(filename, 'wb')) encoder.start() for a in actions: print("add_pcm: %r"%a) encoder.add_pcm(a) encoder.finish() print("render() finished!")
async def ensure_playing(self): async with self.cond: if not self.load_future: print('Waiting to play %s' % trackId) self.audio_fetcher.semaphore.acquire() print('Playing %s' % trackId) self.lame = Lame(**lame_args) self.lame.init_params() self.audio_fetcher.sink.target = self load_future = self.audio_fetcher.player.load( SpotifyId(trackId)) self.load_future = asyncio.futures.wrap_future( load_future, loop=self.audio_fetcher.loop) def done_cb(fut): x = asyncio.run_coroutine_threadsafe( self._load_complete(fut.exception()), loop=self.audio_fetcher.loop) self.load_future.add_done_callback(done_cb)
def build_entire_track(dest): """Build the entire-track file, saving to dest""" with open(dest,"wb") as f: encoder = Lame(ofile=f) print("Building...") encoder.start() mixer = Mixer(None, None) for idx,track in enumerate(database.get_many_mp3(order_by="sequence,id")): print("Adding [%d]: ##%d %s (%r)"%(idx,track.id,track.track_details["artist"],track.filename)) mixer.add_track(track) for actions in mixer.generate_tracks(): print("Encoder: Got %d actions"%len(actions)) for a in actions: print("Encoder: Adding %r"%(a,)) encoder.add_pcm(a) encoder.finish() print("Build complete.")
class Mixer(multiprocessing.Process): def __init__(self, oqueue, infoqueue): self.infoqueue = infoqueue self.encoder = None self.oqueue = oqueue self.__track_lock = threading.Lock() self.__tracks = [] self.transition_time = 30 if not test else 5 self.__stop = False multiprocessing.Process.__init__(self) @property def tracks(self): self.__track_lock.acquire() tracks = self.__tracks self.__track_lock.release() return tracks @tracks.setter def tracks(self, new_val): self.__track_lock.acquire() self.__tracks = new_val self.__track_lock.release() @property def current_track(self): return self.tracks[0] def get_stream(self, x): for fname in (x.filename, "audio/"+x.filename): if os.path.isfile(fname): return fname # TODO: Fetch the contents from the database and save to fname raise NotImplementedError def analyze(self, x): if isinstance(x, list): return [self.analyze(y) for y in x] if isinstance(x, AudioData): return self.process(x) if isinstance(x, tuple): return self.analyze(*x) log.info("Grabbing stream [%r]...", x.id) saved = database.get_analysis(x.id) laf = LocalAudioStream(self.get_stream(x), saved) if not saved: database.save_analysis(x.id, base64.b64encode(cPickle.dumps(laf.analysis,-1))) setattr(laf, "_metadata", x) return self.process(laf) def add_track(self, track): self.tracks.append(self.analyze(track)) def process(self, track): if not hasattr(track.analysis.pyechonest_track, "title"): setattr(track.analysis.pyechonest_track, "title", track._metadata.track_details['title']) log.info("Resampling features [%r]...", track._metadata.id) if len(track.analysis.beats): track.resampled = resample_features(track, rate='beats') track.resampled['matrix'] = timbre_whiten(track.resampled['matrix']) else: log.info("no beats returned for this track.") track.resampled = {"rate":'beats', "matrix": []} track.gain = self.__db_2_volume(track.analysis.loudness) log.info("Done processing [%r].", track._metadata.id) return track def __db_2_volume(self, loudness): return (1.0 - LOUDNESS_THRESH * (LOUDNESS_THRESH - loudness) / 100.0) def generate_tracks(self): """Yield a series of lists of track segments - helper for run()""" while len(self.tracks) < 2: log.info("Waiting for a new track.") track = database.get_track_to_play() try: self.add_track(track) log.info("Got a new track.") except Exception: # TODO: Why? log.error("Exception while trying to add new track:\n%s", traceback.format_exc()) # Initial transition. # yield initialize(self.tracks[0], self.tracks[1]) mixer_state = {} while not self.__stop: while len(self.tracks) > 1: tra = managed_transition(self.tracks[0], self.tracks[1], mixer_state) del self.tracks[0].analysis gc.collect() yield tra log.debug("Finishing track 0 [%r]",self.tracks[0]) from datetime import datetime now = datetime.now().time() self.tracks[0].finish() del self.tracks[0] gc.collect() if self.infoqueue is None: break # Hack: If we're not in infinite mode, don't wait for more tracks. log.info("Waiting for a new track.") try: self.add_track(database.get_track_to_play()) log.info("Got a new track.") except ValueError: log.warning("Track too short! Trying another.") except Exception: log.error("Got an Exception while trying to add new track:\n%s", traceback.format_exc()) log.error("Stopping!") # Last chunk. Should contain 1 instruction: fadeout. # CJA 20150227: Seems to be broken. Commenting this out may mean we ignore the # last track's transition info when building MajorGlitch.mp3, but this is not # serious. The track itself is correctly rendered; it will simply go on until # it reaches the end, and then stop, as per the King's advice. # yield terminate(self.tracks[-1], FADE_OUT) def run(self): database.reset_played() self.encoder = Lame(oqueue=self.oqueue) self.encoder.start() try: self.ctime = None for actions in self.generate_tracks(): log.info("Rendering audio data for %d actions.", len(actions)) for a in actions: try: with Timer() as t: # TODO: Move the "multiple encoding" support into # LAME itself - it should be able to multiplex the # streams itself. self.encoder.add_pcm(a) if self.infoqueue: self.infoqueue.put(generate_metadata(a)) log.info("Rendered in %fs!", t.ms) except Exception: log.error("Could not render %s. Skipping.\n%s SEE???", a, traceback.format_exc()) gc.collect() except Exception: log.error("Something failed in mixer.run:\n%s", traceback.format_exc()) self.stop() return def stop(self): self.__stop = True @property def stopped(self): return self.__stop
class reader: def __init__(self, audio_fetcher): self.audio_fetcher = audio_fetcher self.lame = None self.buf = b'' self.load_future = None self.finished = False self.error = False self.cond = asyncio.Condition() async def ensure_playing(self): async with self.cond: if not self.load_future: print('Waiting to play %s' % trackId) self.audio_fetcher.semaphore.acquire() print('Playing %s' % trackId) self.lame = Lame(**lame_args) self.lame.init_params() self.audio_fetcher.sink.target = self load_future = self.audio_fetcher.player.load( SpotifyId(trackId)) self.load_future = asyncio.futures.wrap_future( load_future, loop=self.audio_fetcher.loop) def done_cb(fut): x = asyncio.run_coroutine_threadsafe( self._load_complete(fut.exception()), loop=self.audio_fetcher.loop) self.load_future.add_done_callback(done_cb) async def read(self, offset, length, full=False): await self.ensure_playing() async with self.cond: min_buf_size = offset + length if full else offset + 1 # Wait until: Enough data is available -OR- the strean is complete -OR- there was an error await self.cond.wait_for( lambda: self.finished or len(self.buf) >= min_buf_size) # raise exception if there was an error before the desired chunk was fetched if len(self.buf) < min_buf_size: self.load_future.result() # return the desired chunk return self.buf[offset:offset + length] async def _load_chunk(self, buf): #Append data to encoding encoded = self.lame.encode_buffer(buf) async with self.cond: self.buf += encoded self.cond.notify_all() async def _load_complete(self, error): print('Audio stream ended:', trackId) # Release audio_fetcher to play next track self.audio_fetcher.sink.target = None self.audio_fetcher.semaphore.release() #Finish encoding encoded = self.lame.encode_flush_nogap() async with self.cond: self.buf += encoded self.finished = True if self.error is None and error: self.error = error self.cond.notify_all() print('Audio stream ended2') async def close(self): print('File reader closed for %s' % trackId) async with self.cond: if not self.finished: self.error = asyncio.CancelledError() if self.load_future: self.audio_fetcher.player.stop() async def wait(self): await self.ensure_playing() async with self.cond: await self.cond.wait_for(lambda: self.finished) if self.error: raise Exception("Failed to fetch music") return self.buf