def header(cls): return MusicObject.header_ui(_("Title"), _("Album"), _("Artist"), _("Length"), _("Rating"), weights=cls.ui_weights)
def restore_history(self): try: with open(HISTORY_FILE, "r") as f: self.history = deserialize(f.read()) except (AttributeError, FileNotFoundError) as e: logging.exception(e) print(_("failed to restore recently played. :("))
def get_text(self): if self.app.current_song is None: return _("Idle") progress, total = self.get_prog_tot() song = self.app.current_song rating = "" if isinstance(song, Song): artist = song.artist if song.rating in (1, 5): rating = "(" + RATE_UI[song.rating] + ")" else: # YTVideo artist = song.channel return " {} {} - {} {}[{:d}:{:02d} / {:d}:{:02d}] {}".format( ["■", "▶"][self.app.play_state == "play"], artist, self.app.current_song.title, rating, *sec_to_min_sec(progress), *sec_to_min_sec(total), self.vol_inds[self.app.volume], )
def restore_queue(self): try: with open(QUEUE_FILE, "r") as f: self.queue_panel.add_songs_to_queue(deserialize(f.read())) except (AttributeError, FileNotFoundError) as e: logging.exception(e) print(_("failed to restore queue. :(")) self.queue_panel.clear()
def save_queue(self): print(_("saving queue")) queue = [] if self.current_song is not None: queue.append(self.current_song) queue.extend(self.queue_panel.queue) with open(QUEUE_FILE, "w") as f: f.write(serialize(queue))
def update_search_results(self, *categories, title=None, isprevsong=False): if title is None: title = _("Search Results") if not self.viewing_previous_songs: # only remember search history self.search_history.append( (self.get_focus()[1], self.search_results)) self.viewing_previous_songs = isprevsong self.set_search_results(categories) self.line_box.set_title(title)
def back(self): if self.search_history: prev_focus, search_history = self.search_history.pop() self.set_search_results(list(search_history)) self.viewing_previous_songs = False self.line_box.set_title(_("Search Results")) try: self.set_focus(prev_focus) except: pass
def login(self): self.g_api = gmusicapi.Mobileclient(debug_logging=False) self.load_config() if not isfile(CRED_FILE): from oauth2client.client import FlowExchangeError print(_("No local credentials file found.")) print( _("TUIJam will now open a browser window so you can provide")) print( _("permission for TUIJam to access your Google Play Music account." )) input(_("Press enter to continue.")) try: self.g_api.perform_oauth(CRED_FILE, open_browser=True) except FlowExchangeError: raise RuntimeError(_("Oauth authentication Failed.")) self.g_api.oauth_login(self.g_api.FROM_MAC_ADDRESS, CRED_FILE, locale=locale.getdefaultlocale()[0]) if self.lastfm_sk is not None: try: self.lastfm = LastFMAPI(self.lastfm_sk) except Exception: print(_("Could not retrieve Last.fm keys.")) print(_("Scrobbling will not be available.")) # TODO handle if sk is invalid from apiclient.discovery import build try: developer_key, = lookup_keys("GOOGLE_DEVELOPER_KEY") self.youtube = build("youtube", "v3", developerKey=developer_key) except Exception: self.youtube = None print(_("Could not retrieve YouTube key.")) print(_("YouTube will not be available."))
def configure(): from os.path import join, isfile from getpass import getpass config_file = join(CONFIG_DIR, "config.yaml") if not isfile(config_file): print(_("It seems that you haven't run tuijam yet.")) print(_("Please run it first, then authorize to Last.fm.")) return print(_("generating Last.fm authentication token")) api = LastFMAPI() token = api.get_token() auth_url = api.get_auth_url(token) import webbrowser webbrowser.open_new_tab(auth_url) print() print( _("Please open this link in your browser and authorize the app in case the window " ).join(_("hasn't been opened automatically:"))) print(auth_url) print() input(_("After that, press Enter to get your session key...")) if not api.auth_by_token(token): print(_("Failed to get a session key. Have you authorized?")) else: with open(config_file, "r+") as f: lastfm_sk = api.sk config = yaml.safe_load(f.read()) config.update({"lastfm_sk": lastfm_sk}) f.seek(0) yaml.safe_dump(config, f, default_flow_style=False) f.truncate() f.close() print(_("Successfully authenticated."))
def listen_now(self): situations = self.g_api.get_listen_now_situations() items = self.g_api.get_listen_now_items() playlists = self.g_api.get_all_user_playlist_contents() liked = self.g_api.get_top_songs() situations = [Situation.from_dict(hit) for hit in situations] albums = [ Album.from_dict(hit["album"]) for hit in items if "album" in hit ] radio_stations = [ RadioStation.from_dict(hit["radio_station"]) for hit in items if "radio_station" in hit ] playlists = [Playlist.from_dict(playlist) for playlist in playlists] liked = [Song.from_dict(song) for song in liked] playlists.append(Playlist(_("Liked"), liked, None)) self.search_panel.update_search_results([], albums, [], situations, radio_stations, playlists, []) self.set_focus(self.search_panel_wrapped)
def main(): import argparse load_locale() parser = argparse.ArgumentParser( "TUIJam", description=_("A fancy TUI client for Google Play Music.")) parser.add_argument("action", choices=["", "configure_last_fm"], default="", nargs="?") parser.add_argument("-v", "--verbose", action="store_true") # TODO: use this args = parser.parse_args() print(_("starting up.")) makedirs(CONFIG_DIR, exist_ok=True) log_file = join(CONFIG_DIR, "log.txt") logging.basicConfig(filename=log_file, filemode="w", level=logging.WARNING) if args.action == "configure_last_fm": LastFMAPI.configure() exit(0) elif args.action != "": print(f"Unrecognized option: {args.action}") exit(0) app = App() print(_("logging in.")) app.login() if app.mpris_enabled: from .mpris import setup_mpris print(_("enabling external control.")) app.mpris = setup_mpris(app) if not app.mpris: print(_("Failed.")) if app.persist_queue: print(_("restoring queue")) app.restore_queue() print(_("restoring history")) app.restore_history() if app.video: app.player["vid"] = "auto" import signal signal.signal(signal.SIGINT, app.cleanup) loop = urwid.MainLoop(app, event_loop=urwid.GLibEventLoop()) loop.screen.set_terminal_properties(256) loop.screen.register_palette([(k.replace("-", " "), "", "", "", fg, bg) for k, (fg, bg) in palette.items()]) app.loop = loop try: loop.run() except Exception as e: logging.exception(e) print( _("Something bad happened! :( see log file ($HOME/.config/tuijam/log.txt) for more information." )) app.cleanup()
def fmt_str(self): return [("np_song", f"{self.title} "), _("by "), ("np_artist", f"{self.artist}")]
def __str__(self): return "{} {}{}".format(self.title, _("by "), self.artist)
def header(cls): return MusicObject.header_ui(_("Playlist Name"), _("# Songs"), weights=cls.ui_weights)
def header(): return MusicObject.header_ui(_("Station Name"))
def header(): return MusicObject.header_ui(_("Situation"), _("Description"))
def header(): return MusicObject.header_ui(_("Artist"))
def header(): return MusicObject.header_ui(_("Album"), _("Artist"), _("Year"))
def header(cls): return MusicObject.header_ui(_("Youtube"), _("Channel"), weights=cls.ui_weights)
def __str__(self): return "{} {}{}".format(self.title, _("by "), self.channel)
def view_previous_songs(self, songs, yt_vids): self.update_search_results(songs, yt_vids, title=_("Previous Songs"), isprevsong=True)
def __init__(self, app): self.app = app super().__init__(_("search > "), multiline=False, allow_tab=False)
def __init__(self): import mpv self.player = mpv.MPV() self.player.volume = 100 self.player["vid"] = "no" self.volume = 8 self.g_api = None self.loop = None self.config_pw = None self.reached_end_of_track = False self.lastfm = None self.youtube = None self.mpris = None self.vim_mode = None self.vim_insert = False @self.player.event_callback("end_file") def end_file_callback(event): if event["event"]["reason"] == 0: self.reached_end_of_track = True if self.lastfm: self.current_song.lastfm_scrobbled = False self.schedule_refresh(dt=0.01) self.search_panel = SearchPanel(self) search_panel_wrapped = urwid.LineBox(self.search_panel, title=_("Search Results")) # Give search panel reference to LineBox to change the title dynamically self.search_panel.line_box = search_panel_wrapped search_panel_wrapped = urwid.AttrMap(search_panel_wrapped, "region_bg normal", "region_bg select") self.search_panel_wrapped = search_panel_wrapped self.playbar = PlayBar(self, "progress_remaining", "progress", current=0, done=100) self.queue_panel = QueuePanel(self) queue_panel_wrapped = urwid.LineBox(self.queue_panel, title=_("Queue")) queue_panel_wrapped = urwid.AttrMap(queue_panel_wrapped, "region_bg normal", "region_bg select") self.queue_panel_wrapped = queue_panel_wrapped self.search_input = urwid.Edit("> ", multiline=False) self.search_input = SearchInput(self) urwid.Pile.__init__( self, [ ("weight", 12, search_panel_wrapped), ("pack", self.playbar), ("weight", 7, queue_panel_wrapped), ("pack", self.search_input), ], ) self.set_focus(self.search_input) self.play_state = "stop" self.current_song = None self.history = []