Example #1
0
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!")
Example #2
0
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.")
Example #3
0
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