Example #1
0
	def start(self):
		# parse arguments
		parser = argparse.ArgumentParser(description="Plays music from Songza in your terminal")
		# parser.add_argument("--query", metavar="Query Text", help="Query text used to search for stations; the app will start with query results pre-populated")
		parser.add_argument("--station_id", metavar="1234567", help="This is the station ID used internally by Songza")
		parser.add_argument("--debug", action="store_true", help="Add this flag to dump debug info to a file.")

		args = parser.parse_args()

		# instatiate a player and station
		self.player = VlcPlayer(args.debug)
		self.station = Station(self.player, 0, args.debug)

		# start streaming music, if station id was provided
		if args.station_id:
			self.station.station_id = args.station_id
			self.play_next()
			self.station.update_track_info()

		# build out the screen
		self.setup_screen()

		palette = [
			("bold", "default,bold", "default"),
			("reversed", "standout", ""),
			("logo", "default", "black")
		]

		# start the run loop
		loop = urwid.MainLoop(self.ui["container"], palette, unhandled_input=self.handle_input)
		loop.set_alarm_in(1, self.stream)
		loop.set_alarm_in(.5, self.update_player_ui)
		loop.run()
Example #2
0
from player import VlcPlayer

player = VlcPlayer()
player.play("a-side.mp4")

i = 0

while True:
	print str(i) + ":\n"
	print player.send_command_readline("get_time\n")
	i += 1
Example #3
0
class MixZaTape:
	def __init__(self):
		# key handlers are set here
		# TODO: add alternate key mappings, VIM-style, possibly even user configurable?
		self.key_handlers = {
			# ESC key
			"esc": ("Exit", self.exit),
			"f9": ("Skip", self.skip),
			"+": ("Volume Up", self.volume_up),
			"-": ("Volume Down", self.volume_down),
			# space bar
			"f8": ("Pause", self.pause),
			"f10": ("Seek", self.seek),
			"f1": ("Help", self.show_help),
			"f2": ("Station Search", self.show_search),
			">": ("Upvote", self.upvote),
			"<": ("Downvote", self.downvote),
		}

		# UI text
		self.ui_text = {
			"current_track":	"Playing:  ",
			"last_track":		"Previous: ",
			"select_station":	"Select a Station",
			"help_controls":	"Controls",
			"current_station":	"Station: "
		}

		# save_file
		# =========
		# Used to save & persist app data between sessions
		self.save_file = "./.save"

		# save_data
		# =========
		# Assorted app data that we want to be persisted
		self.save_data = {}

	# setup_screen()
	# ==============
	# Builds and sets up the major screen components
	def setup_screen(self):

		# init main screen
		self.ui = {
			"track_info": urwid.Text(""),
			"station_info": urwid.Text("{0}{1}".format(self.ui_text["current_station"], "None")),
			"time_left": urwid.Text(""),
			"progress_bar": urwid.Text(""),
			"help_screen": self.build_help_screen(),
			"search_screen": self.build_search_screen(),
			"logo": self.build_logo(),
			"footer": urwid.Text("")
		}
		window_walker = urwid.SimpleListWalker([
			urwid.Divider(),
			self.ui["track_info"],
			urwid.Divider(),
			self.ui["time_left"],
			self.ui["progress_bar"],
			urwid.Divider(u"\u2501", 1, 1),
			self.ui["station_info"],
			urwid.Divider(u"\u2501", 1, 1),
			self.ui["help_screen"]
		])

		# set default focus on last element in the list box
		i = len(window_walker.positions()) - 1
		window_walker.set_focus(i)

		# add widgets to main window
		window = urwid.ListBox(window_walker)
		self.ui["window"] = window
		self.ui["window_walker"] = window_walker
		
		# build frame
		self.ui["container"] = urwid.Frame(window, self.ui["logo"], self.ui["footer"], focus_part="body")

		# attempt to load any previously saved app data
		self.load_state()
		
	# exit()
	# ======
	# Exits the app, cleaning up GUI elements and stopping the player.
	def exit(self):
		# stop the music
		self.player.stop()
		urwid.ExitMainLoop();

		sys.exit()

	# start()
	# =======
	# Bootstraps by parsing arguments and such.
	# Starts the main run loop and binds event handlers.
	def start(self):
		# parse arguments
		parser = argparse.ArgumentParser(description="Plays music from Songza in your terminal")
		# parser.add_argument("--query", metavar="Query Text", help="Query text used to search for stations; the app will start with query results pre-populated")
		parser.add_argument("--station_id", metavar="1234567", help="This is the station ID used internally by Songza")
		parser.add_argument("--debug", action="store_true", help="Add this flag to dump debug info to a file.")

		args = parser.parse_args()

		# instatiate a player and station
		self.player = VlcPlayer(args.debug)
		self.station = Station(self.player, 0, args.debug)

		# start streaming music, if station id was provided
		if args.station_id:
			self.station.station_id = args.station_id
			self.play_next()
			self.station.update_track_info()

		# build out the screen
		self.setup_screen()

		palette = [
			("bold", "default,bold", "default"),
			("reversed", "standout", ""),
			("logo", "default", "black")
		]

		# start the run loop
		loop = urwid.MainLoop(self.ui["container"], palette, unhandled_input=self.handle_input)
		loop.set_alarm_in(1, self.stream)
		loop.set_alarm_in(.5, self.update_player_ui)
		loop.run()

	# handle_input(key)
	# =================
	def handle_input(self, key):
		# fire handler for the input key
		handler = self.key_handlers.get(key)
		if (handler is not None):
			handler[1]()

	# update_player_ui(loop, user_data)
	# =================================
	# Updates the player UI (current track, progress, etc).
	def update_player_ui(self, loop, user_data):
		# set a new timer
		loop.set_alarm_in(.5, self.update_player_ui)

		# don't redraw if currently paused	
		if self.is_paused():
			return

		# show the current track if not null or empty
		# remember that the cursor positon moves with the text by default
		if (bool(self.station.current_track)):
			track_text = [
				self.ui_text["current_track"],
				("bold", unicode(self.station.current_track["title"] + " ")),
				unicode(self.station.current_track["artist"]["name"])
			]

			self.ui["track_info"].set_text(track_text)

			# show time remaining
			seconds = int(self.time_remaining())

			# Occasionally, seconds remaining can be negative, especially immediately
			# after un-pausing. Skip this update if that is the case.
			if (seconds < 0):
				return
			duration = int(self.station.current_track["duration"])
			self.ui["time_left"].set_text("{0}:{1:02d} / {2}:{3:02d}".format(
				seconds / 60,
				seconds % 60,
				duration / 60,
				duration % 60
			))

			self.ui["progress_bar"].set_text(self.draw_progress_bar(3, 0, duration - seconds, duration, 50, u"\u2588"))
		else:
			self.ui["track_info"].set_text("")

	# draw_progress_bar(current, startY, startX, total, size, chr)
	# ============================================================
	# Draws a progress bar of the specified size.
	# - current: current value
	# - total: total value
	# - size: size of the bar (in characters)
	# - chr: character to use in drawing
	def draw_progress_bar(self, startY, startX, current, total, size, chr):
		progress = int((current * 1.0 / total) * size)
		bar = "".join([chr] * progress)

		# add caps
		gap = "".join(["-"] * (size - progress))
		bar += gap
		return u"{0}{1}{0}".format(u"|", bar)

	# stream(loop, user_data)
	# =======================
	# The stream() callback is fired in the main run loop to continuously stream music
	def stream(self, loop, user_data):
		if not self.is_paused():
			# check if current song is almost done
			time_left = self.time_remaining()
			
			# unable to accurately read time remaining, usually due to seeking (time == -1)
			if time_left > 0:
				if (time_left <= 5 and self.station.next_track == None):
					self.play_next()

				if (time_left <= 1 and self.station.next_track is not None):
					self.update_track_info()

		loop.set_alarm_in(1, self.stream)

	# build_logo()
	# ============
	def build_logo(self):
		text = urwid.Text(("reversed", "\n|[●▪▪●]| MixZaTape\n"))
		text.set_align_mode("center")
		return urwid.AttrMap(text, "logo")
		# return urwid.Filler(text, "bottom")

	# build_station_list(query)
	# ========================
	# Fires off a query for stations with the specified query test
	# and builds a select list of stations.
	def build_station_list(self, query):
		# build menu options
		# flatten down to just name => id dictionary
		menu_opts = {}
		stations = self.station.query_station(query)
		for station in stations:
			menu_opts[station["name"]] = station["id"]

		body = [urwid.Text(self.ui_text["select_station"]), urwid.Divider()]
		for k in menu_opts.keys():
			button = urwid.Button(k)
			urwid.connect_signal(button, "click", self.on_station_selected, (k, menu_opts[k]))
			body.append(urwid.AttrMap(button, None, focus_map="reversed"))

		return urwid.BoxAdapter(urwid.ListBox(urwid.SimpleFocusListWalker(body)), 20)

	# build_help_screen()
	# ===================
	# Build the help screen.
	def build_help_screen(self):
		body = [urwid.Text(("bold", self.ui_text["help_controls"])), urwid.Divider()]
	
		# display control keys in alphabetical order
		keys = self.key_handlers.keys()
		keys.sort()
		for k in keys:
			handler = self.key_handlers[k]
			body.append(urwid.Text("[{0}]: {1}".format(k, handler[0])))

		return urwid.Pile(body)

	# build_search_screen()
	# =====================
	# Build the station search screen.
	def build_search_screen(self):
		input = StationSearchBox("Station Search: ", "")
		urwid.connect_signal(input, "keypress", self.on_search_keypress)

		body = [
			input
		]

		return urwid.Pile(body)
		

	# handler for selected station
	def on_station_selected(self, button, key_value):
		self.change_station(key_value[0], key_value[1])

	# custom keypress handler for the station list
	def on_search_keypress(self, widget, size, key):
		# submit query on enter
		if (key == "enter"):
			station_list = self.build_station_list(widget.get_edit_text())
			self.show_screen(station_list)
		# allow controls keys to execute
		elif (key in self.key_handlers):
			self.key_handlers[key][1]()

	# show_screen(screen)
	# ===================
	# Inserts the specified screen into the main window.
	def show_screen(self, screen):
		i = len(self.ui["window_walker"].positions()) - 1
		self.ui["window_walker"].contents[i] = screen
		self.ui["window_walker"].set_focus = i

	# show_help()
	# ===========
	# Display the help screen.
	def show_help(self):
		self.show_screen(self.ui["help_screen"])

	# show_search()
	# =============
	# Display the search screen.
	def show_search(self):
		self.show_screen(self.ui["search_screen"])

	# change_station(station_name, station_id)
	# ========================================
	def change_station(self, station_name, station_id):
		# set ui components with new station info
		self.ui["station_info"].set_text(
			"{0}{1} ({2})".format(
				self.ui_text["current_station"],
				station_name,
				station_id
			)
		)
		self.station.change_station(station_name, station_id)
		self.skip()

		# save current station info
		self.save_data["station_id"] = station_id
		self.save_data["station_name"] = station_name
		self.save_state()

	# set_status_line(message)
	# ========================
	# Sets the status in the footer.
	def set_status_line(self, message):
		self.ui["footer"].set_text(message)
		

	# save_state()
	# ============
	# Saves the current state of the app to file
	def save_state(self):
		with open(self.save_file, "w") as file:
			file.write(json.dumps(self.save_data))

	# load_state()
	# ============
	# Loads the current state of the app from file
	def load_state(self):
		# attempt to load file
		try:
			with open(self.save_file, "r") as file:
				text = file.read()
				if len(text) > 0:
					self.save_data = json.loads(text)
					self.change_station(self.save_data["station_name"], self.save_data["station_id"])
		except Exception as ex:
			print(str(ex))

		
	# the below functions are wrappers for the station and player classes

	def play_next(self):
		self.station.play_next()

	def skip(self):
		# TODO some sort of status indicator on skip? Or limit skips?
		self.play_next()
		self.player.skip()
		self.update_track_info()

	def update_track_info(self):
		self.station.update_track_info()

	def replay_last(self):
		# TODO: implement this
		self.station.play_next()

	def volume_up(self):
		self.player.volume_up()

	def volume_down(self):
		self.player.volume_down()

	def pause(self):
		self.player.pause()

	def is_paused(self):
		return self.player.is_paused

	def time_remaining(self):
		# return self.station.time_remaining()
		return self.player.time_remaining()

	def seek(self):
		return self.player.seek(self.player.get_time() + 5)

	# upvote current track
	def upvote(self):
		if bool(self.station.current_track):
			self.station.vote(self.station.current_track["id"], True)

			# show status
			self.set_status_line("Upvoted: {0}".format(self.station.current_track["title"]))

	# downvote current track
	# and skip it
	def downvote(self):
		if bool(self.station.current_track):
			self.station.vote(self.station.current_track["id"], False)
			# show status
			self.set_status_line("Downvoted: {0}".format(self.station.current_track["title"]))
			self.skip()