def __init__(self, tauon): self.tauon = tauon self.strings = tauon.strings self.start_timer = Timer() self.status = 0 self.spotify = None self.loaded_art = "" self.playing = False self.coasting = False self.paused = False self.token = None self.cred = None self.started_once = False self.redirect_uri = f"http://localhost:7811/spotredir" self.current_imports = {} self.spotify_com = False self.sender = None self.cache_saved_albums = [] self.scope = "user-read-playback-position streaming user-modify-playback-state user-library-modify user-library-read user-read-currently-playing user-read-playback-state playlist-read-private playlist-modify-private playlist-modify-public" self.launching_spotify = False self.progress_timer = Timer() self.update_timer = Timer() self.token_path = os.path.join(self.tauon.user_directory, "spot-token-pkce") self.pkce_code = None
def __init__(self, tauon): self.tauon = tauon self.strings = tauon.strings self.start_timer = Timer() self.status = 0 self.spotify = None self.loaded_art = "" self.playing = False self.coasting = False self.paused = False self.token = None self.cred = None self.started_once = False self.redirect_uri = f"http://localhost:7811/spotredir" self.current_imports = {} self.spotify_com = False self.progress_timer = Timer() self.update_timer = Timer() self.token_path = os.path.join(self.tauon.user_directory, "spot-r-token")
def __init__(self, tauon): self.tauon = tauon self.pctl = tauon.pctl self.prefs = tauon.prefs self.gui = tauon.gui self.scanning = False self.connected = False self.accessToken = None self.userId = None self.currentId = None self.session_thread_active = False self.session_status = 0 self.session_item_id = None self.session_update_timer = Timer() self.session_last_item = None
def __init__(self): self.encoder = None self.decoder = None self.raw_buffer = io.BytesIO() self.raw_buffer_size = 0 self.output_buffer = io.BytesIO() self.output_buffer_size = 0 self.temp_buffer = io.BytesIO() self.temp_buffer_size = 0 self.stream_time = Timer() self.bytes_sent = 0 self.track_bytes_sent = 0 self.dry = 0
def __init__(self): # This is used to keep track of time between callbacks to progress the seek bar self.player_timer = Timer() # This is used to keep note of what state of playing we should be in self.play_state = 0 # 0 is stopped, 1 is playing, 2 is paused # Initiate GSteamer Gst.init([]) self.mainloop = GLib.MainLoop() # Create main "playbin" pipeline thingy for simple playback self.pl = Gst.ElementFactory.make("playbin", "player") # Set callback for the main callback loop GLib.timeout_add(500, self.main_callback) # self.pl.connect("about-to-finish", self.about_to_finish) self.mainloop.run()
def __init__(self): print("Init GStreamer...") # This is used to keep track of time between callbacks. self.player_timer = Timer() # Store the track object that is currently playing self.loaded_track = None # This is used to keep note of what state of playing we should be in self.play_state = 0 # 0 is stopped, 1 is playing, 2 is paused # Initiate GSteamer Gst.init([]) self.mainloop = GLib.MainLoop() # Populate list of output devices with defaults outputs = {} devices = [ "PulseAudio", "ALSA", "JACK", ] if tauon.snap_mode: # Snap permissions don't support these by default devices.remove("JACK") devices.remove("ALSA") # Get list of available audio device self.dm = Gst.DeviceMonitor() self.dm.start() for device in self.dm.get_devices(): if device.get_device_class() == "Audio/Sink": element = device.create_element(None) type_name = element.get_factory().get_name() if hasattr(element.props, "device"): device_name = element.props.device display_name = device.get_display_name() # This is used by the UI to present list of options to the user in audio settings outputs[display_name] = (type_name, device_name) devices.append(display_name) # dm.stop() # Causes a segfault sometimes pctl.gst_outputs = outputs pctl.gst_devices = devices # Create main "playbin" pipeline for playback self.playbin = Gst.ElementFactory.make("playbin", "player") # Create custom output bin from user preferences if not prefs.gst_use_custom_output: prefs.gst_output = prefs.gen_gst_out() self._output = Gst.parse_bin_from_description( prefs.gst_output, ghost_unlinked_pads=True) # Create a bin for the audio pipeline self._sink = Gst.ElementFactory.make("bin", "sink") self._sink.add(self._output) # Spectrum ------------------------- # Cant seem to figure out how to process these magnitudes in a way that looks good # self.spectrum = Gst.ElementFactory.make("spectrum", "spectrum") # self.spectrum.set_property('bands', 20) # self.spectrum.set_property('interval', 10000000) # self.spectrum.set_property('threshold', -100) # self.spectrum.set_property('post-messages', True) # self.spectrum.set_property('message-magnitude', True) # # self.playbin.set_property('audio-filter', self.spectrum) # # ------------------------------------ # # Level Meter ------------------------- self.level = Gst.ElementFactory.make("level", "level") self.level.set_property('interval', 20000000) self.playbin.set_property('audio-filter', self.level) # # ------------------------------------ self._eq = Gst.ElementFactory.make("equalizer-nbands", "eq") self._vol = Gst.ElementFactory.make("volume", "volume") self._sink.add(self._eq) self._sink.add(self._vol) self._eq.link(self._vol) self._vol.link(self._output) self._eq.set_property("num-bands", 10 + 2) # Set the equalizer based on user preferences # Using workaround for "inverted slider" bug. # Thanks to Lollypop and Clementine for discovering this. # Ref https://github.com/Taiko2k/TauonMusicBox/issues/414 for i in range(10 + 2): band = self._eq.get_child_by_index(i) band.set_property("freq", 0) band.set_property("bandwidth", 0) band.set_property("gain", 0) self.set_eq() # if prefs.use_eq: # self._eq.set_property("band" + str(i), level * -1) # else: # self._eq.set_property("band" + str(i), 0.0) # Set up sink pad for the intermediate bin via the # first element (volume) ghost = Gst.GhostPad.new("sink", self._eq.get_static_pad("sink")) self._sink.add_pad(ghost) # Connect the playback bin to to the intermediate bin sink pad self.playbin.set_property("audio-sink", self._sink) # The pipeline should look something like this - # (player) -> [(eq) -> (volume) -> (output)] # Create controller for pause/resume volume fading self.c_source = GstController.InterpolationControlSource() self.c_source.set_property('mode', GstController.InterpolationMode.LINEAR) self.c_binding = GstController.DirectControlBinding.new( self._vol, "volume", self.c_source) self._vol.add_control_binding(self.c_binding) # Set callback for the main callback loop GLib.timeout_add(50, self.main_callback) self.playbin.connect("about-to-finish", self.about_to_finish) # Not used # Setup bus and select what types of messages we want to listen for bus = self.playbin.get_bus() bus.add_signal_watch() bus.connect('message::element', self.on_message) bus.connect('message::buffering', self.on_message) bus.connect('message::error', self.on_message) bus.connect('message::tag', self.on_message) bus.connect('message::warning', self.on_message) # bus.connect('message::eos', self.on_message) # Variables used with network downloading self.temp_id = "a" self.url = None self.dl_ready = True self.using_cache = False self.temp_path = "" # Full path + filename # self.level_train = [] self.seek_timer = Timer() self.seek_timer.force_set(10) self.buffering = False # Other self.end_timer = Timer() # Start GLib mainloop self.mainloop.run()
def __init__(self): # This is used to keep track of time between callbacks to progress the seek bar self.player_timer = Timer() # This is used to keep note of what state of playing we should be in self.play_state = 0 # 0 is stopped, 1 is playing, 2 is paused # Initiate GSteamer Gst.init([]) self.mainloop = GLib.MainLoop() # Get list of available audio device pctl.gst_devices = ["Auto", "PulseAudio", "ALSA", "JACK"] if tauon.snap_mode: pctl.gst_devices.remove("JACK") pctl.gst_devices.remove("ALSA") pctl.gst_outputs.clear() dm = Gst.DeviceMonitor() dm.start() for device in dm.get_devices(): if device.get_device_class() == "Audio/Sink": element = device.create_element(None) type_name = element.get_factory().get_name() device_name = element.props.device display_name = device.get_display_name() # This is used by the UI to present list of options to the user in audio settings pctl.gst_outputs[display_name] = (type_name, device_name) pctl.gst_devices.append(display_name) dm.stop() # Create main "playbin" pipeline for playback self.playbin = Gst.ElementFactory.make("playbin", "player") # Create output bin if not prefs.gst_use_custom_output: prefs.gst_output = prefs.gen_gst_out() self._output = Gst.parse_bin_from_description( prefs.gst_output, ghost_unlinked_pads=True) # Create a bin for the audio pipeline self._sink = Gst.ElementFactory.make("bin", "sink") self._sink.add(self._output) # # Spectrum ------------------------- # # This kind of works, but is a different result to that of the bass backend. # # This seems linear and also less visually appealing. # # self.spectrum = Gst.ElementFactory.make("spectrum", "spectrum") # self.spectrum.set_property('bands', 280) # self.spectrum.set_property('interval', 10000000) # self.spectrum.set_property('post-messages', True) # self.spectrum.set_property('message-magnitude', True) # # self.playbin.set_property('audio-filter', self.spectrum) # # ------------------------------------ # Create volume element self._vol = Gst.ElementFactory.make("volume", "volume") self._sink.add(self._vol) self._vol.link(self._output) # Set up sink pad for the intermediate bin via the # first element (volume) ghost = Gst.GhostPad.new("sink", self._vol.get_static_pad("sink")) self._sink.add_pad(ghost) # Connect the playback bin to to the intermediate bin sink pad self.playbin.set_property("audio-sink", self._sink) # The pipeline should look something like this - # (player) -> [(volume) -> (output)] # Set callback for the main callback loop GLib.timeout_add(50, self.main_callback) # self.playbin.connect("about-to-finish", self.about_to_finish) # Not used by anything # # Enable bus to get spectrum messages bus = self.playbin.get_bus() bus.add_signal_watch() bus.connect('message::element', self.on_message) bus.connect('message::buffering', self.on_message) bus.connect('message::error', self.on_message) bus.connect('message::tag', self.on_message) # bus.connect('message::warning', self.on_message) # bus.connect('message::eos', self.on_message) # Variables used with network downloading self.temp_id = "a" self.url = None self.dl_ready = False self.temp_path = "" # Full path + filename # # Broadcasting pipeline ------------ # # # This works, but only for one track, switching tracks seems to be a more complicated process. # # self.b_playbin = Gst.ElementFactory.make("playbin", "player") # # # Create output bin # # Using tcpserversink seems to mostly work with the html5 player, though an HTTP server may be preferred. # self._b_output = Gst.parse_bin_from_description( # "audioconvert ! vorbisenc ! oggmux ! tcpserversink port=8000", ghost_unlinked_pads=True) # #"autoaudiosink", ghost_unlinked_pads=True) # # # Connect the playback bin to to the output bin # self.b_playbin.set_property("audio-sink", self._b_output) # # ---------------------------------------- # Start GLib mainloop self.mainloop.run()
def webserve2(pctl, prefs, gui, album_art_gen, install_directory, strings, tauon): play_timer = Timer() class Server(BaseHTTPRequestHandler): def run_command(self, callback): self.send_response(200) #self.send_header("Content-type", "application/json") self.end_headers() callback() self.wfile.write(b"OK") def parse_trail(self, text): params = {} both = text.split("?") levels = both[0].split("/") if len(both) > 1: pairs = both[2].split("&") for p in pairs: aa, bb = p.split("=") params[aa] = bb return levels, params def get_track(self, track_position, playlist_index=None, track=None, album_id=-1): if track is None: if playlist_index is None: playlist = pctl.multi_playlist[ pctl.active_playlist_playing][2] else: playlist = pctl.multi_playlist[playlist_index][2] track_id = playlist[track_position] track = pctl.g(track_id) data = {} data["title"] = track.title data["artist"] = track.artist data["album"] = track.album data["album_artist"] = track.album_artist if not track.album_artist: data["album_artist"] = track.artist data["duration"] = int(track.length * 1000) data["id"] = track.index data["position"] = track_position data["album_id"] = album_id data["has_lyrics"] = track.lyrics != "" data["track_number"] = str(track.track_number).lstrip("0") data["can_download"] = not track.is_cue and not track.is_network return data def send_file(self, path, mime): range_req = False start = 0 end = 0 if "Range" in self.headers: range_req = True b = self.headers["Range"].split("=")[1] start, end = b.split("-") start = int(start) with open(path, "rb") as f: f.seek(0, 2) length = f.tell() f.seek(0, 0) l = str(length) remain = length - start if range_req: self.send_response(206) self.send_header("Content-type", mime) self.send_header("Content-Range", f"bytes={start}-/{l}") self.send_header("Content-Length", str(remain)) f.seek(start) else: self.send_response(200) self.send_header("Content-type", mime) self.send_header("Content-Length", l) self.end_headers() while True: data = f.read(5000) if not data: break self.wfile.write(data) def do_GET(self): path = self.path if path.startswith("/api1/pic/small/"): value = path[16:] if value.isalnum() and int(value) in pctl.master_library: track = pctl.g(int(value)) raw = album_art_gen.save_thumb(track, (75, 75), "") if raw: self.send_response(200) self.send_header("Content-type", "image/jpg") self.end_headers() self.wfile.write(raw.read()) else: self.send_response(404) self.end_headers() self.wfile.write(b"No image found") else: self.send_response(404) self.end_headers() self.wfile.write(b"Invalid parameter") if path.startswith("/api1/pic/medium/"): value = path[17:] if value.isalnum() and int(value) in pctl.master_library: track = pctl.g(int(value)) raw = album_art_gen.save_thumb(track, (1000, 1000), "") if raw: self.send_response(200) self.send_header("Content-type", "image/jpg") self.end_headers() self.wfile.write(raw.read()) else: self.send_response(404) self.end_headers() self.wfile.write(b"No image found") else: self.send_response(404) self.end_headers() self.wfile.write(b"Invalid parameter") if path.startswith("/api1/lyrics/"): value = path[13:] if value.isalnum() and int(value) in pctl.master_library: track = pctl.g(int(value)) data = {} data["track_id"] = track.index data["lyrics_text"] = track.lyrics self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() data = json.dumps(data).encode() self.wfile.write(data) else: self.send_response(404) self.end_headers() self.wfile.write(b"Invalid parameter") # elif path.startswith("/api1/stream/"): # param = path[13:] # # if param.isdigit() and int(param) in pctl.master_library: # track = pctl.master_library[int(param)] # mime = "audio/mpeg" # #mime = "audio/ogg" # self.send_response(200) # self.send_header("Content-type", mime) # self.end_headers() # # cmd = ["ffmpeg", "-i", track.fullpath, "-c:a", "libopus", "-f", "ogg", "-"] # #cmd = ["ffmpeg", "-i", track.fullpath, "-c:a", "libvorbis", "-f", "ogg", "-"] # #cmd = ["ffmpeg", "-i", track.fullpath, "-c:a", "libmp3lame", "-f", "mp3", "-"] # encoder = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) # while True: # data = encoder.stdout.read(1024) # if data: # self.wfile.write(data) elif path.startswith("/api1/playinghit/"): param = path[17:] if param.isdigit() and int(param) in pctl.master_library: t = play_timer.hit() if 0 < t < 5: tauon.star_store.add(int(param), t) self.send_response(200) self.send_header("Content-type", "text/plain") self.end_headers() self.wfile.write(b"OK") elif path.startswith("/api1/file/"): param = path[11:] #print(self.headers) play_timer.hit() if param.isdigit() and int(param) in pctl.master_library: track = pctl.master_library[int(param)] mime = "audio/mpeg" if track.file_ext == "FLAC": mime = "audio/flac" if track.file_ext == "OGG" or track.file_ext == "OPUS" or track.file_ext == "OGA": mime = "audio/ogg" if track.file_ext == "M4A": mime = "audio/mp4" self.send_file(track.fullpath, mime) elif path.startswith("/api1/start/"): levels, _ = self.parse_trail(path) if len(levels) == 5: playlist = levels[3] position = levels[4] if playlist.isdigit() and position.isdigit(): position = int(position) playlist = int(playlist) pl = tauon.id_to_pl(int(playlist)) if pl is not None and pl < len(pctl.multi_playlist): playlist = pctl.multi_playlist[pl][2] if position < len(playlist): tauon.switch_playlist(pl, cycle=False, quiet=True) pctl.jump(playlist[position], position) self.send_response(200) self.send_header("Content-type", "text/plain") self.end_headers() self.wfile.write(b"OK") elif path == "/api1/play": self.run_command(tauon.pctl.play) elif path == "/api1/pause": self.run_command(tauon.pctl.pause_only) elif path == "/api1/next": self.run_command(tauon.pctl.advance) elif path == "/api1/back": self.run_command(tauon.pctl.back) elif path == "/api1/shuffle": self.run_command(tauon.toggle_random) elif path == "/api1/repeat": self.run_command(tauon.toggle_repeat) elif path == "/api1/version": data = {"version": 1} self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() data = json.dumps(data).encode() self.wfile.write(data) elif path == "/api1/playlists": l = [] for item in pctl.multi_playlist: p = {} p["name"] = item[0] p["id"] = str(item[6]) p["count"] = len(item[2]) l.append(p) data = {"playlists": l} self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() data = json.dumps(data).encode() self.wfile.write(data) elif path.startswith("/api1/albumtracks/"): # Get tracks that appear in an album /albumtracks/plid/albumid levels, _ = self.parse_trail(path) l = [] if len(levels) == 5 and levels[3].isdigit( ) and levels[4].isdigit(): pl = tauon.id_to_pl(int(levels[3])) if pl is not None: _, album, _ = tauon.get_album_info(int(levels[4]), pl) # print(album) for p in album: l.append( self.get_track(p, pl, album_id=int(levels[4]))) data = {"tracks": l} self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() data = json.dumps(data).encode() self.wfile.write(data) elif path.startswith("/api1/trackposition/"): # get track /trackposition/plid/playlistposition levels, _ = self.parse_trail(path) if len(levels) == 5 and levels[3].isdigit( ) and levels[4].isdigit(): pl = tauon.id_to_pl(int(levels[3])) if pl is not None: data = self.get_track(int(levels[4]), pl) playlist = pctl.multi_playlist[pl][2] p = int(levels[4]) if p < len(playlist): track = pctl.g(playlist[p]) while True: if p < 0 or pctl.g( playlist[p] ).parent_folder_path != track.parent_folder_path: p += 1 break p -= 1 data["album_id"] = p self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() data = json.dumps(data).encode() self.wfile.write(data) else: self.send_response(404) self.send_header("Content-type", "text/plain") self.end_headers() self.wfile.write(b"404 invalid track position") else: self.send_response(404) self.send_header("Content-type", "text/plain") self.end_headers() self.wfile.write(b"404 playlist not found") else: self.send_response(404) self.send_header("Content-type", "text/plain") self.end_headers() self.wfile.write(b"404 invalid track") elif path.startswith("/api1/setvolume/"): key = path[16:] if key.isdigit(): volume = int(key) volume = max(volume, 0) volume = min(volume, 100) pctl.player_volume = volume pctl.set_volume() self.send_response(200) self.send_header("Content-type", "text/plain") self.end_headers() self.wfile.write(b"OK") elif path.startswith("/api1/seek1k/"): key = path[13:] if key.isdigit(): pctl.seek_decimal(int(key) / 1000) self.send_response(200) self.send_header("Content-type", "text/plain") self.end_headers() self.wfile.write(b"OK") elif path.startswith("/api1/tracklist/"): # Return all tracks in a playlist /tracklist/plid key = path[16:] l = [] if key.isdigit(): pl = tauon.id_to_pl(int(key)) if pl is not None and pl < len(pctl.multi_playlist): playlist = pctl.multi_playlist[pl][2] parent = "" album_id = 0 for i, id in enumerate(playlist): tr = pctl.g(id) if i == 0: parent = tr.parent_folder_path elif parent != tr.parent_folder_path: parent = tr.parent_folder_path album_id = i l.append(self.get_track(i, pl, album_id=album_id)) data = {"tracks": l} self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() data = json.dumps(data).encode() self.wfile.write(data) elif path.startswith("/api1/albums/"): # Returns lists of tracks that are start of albums /albums/plid key = path[13:] l = [] if key.isdigit(): pl = tauon.id_to_pl(int(key)) if pl is not None: dex = tauon.reload_albums(True, pl) # print(dex) for a in dex: l.append(self.get_track(a, pl, album_id=a)) data = {"albums": l} self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() data = json.dumps(data).encode() self.wfile.write(data) elif path == "/api1/status": self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() data = { "status": "stopped", "inc": pctl.db_inc, "shuffle": pctl.random_mode == True, "repeat": pctl.repeat_mode == True, "progress": 0, "volume": pctl.player_volume, "playlist": str(tauon.get_playing_playlist_id()), "playlist_length": len(pctl.multi_playlist[pctl.active_playlist_playing][2]) } if pctl.playing_state == 1: data["status"] = "playing" if pctl.playing_state == 2: data["status"] = "paused" track = pctl.playing_object() if track: data["id"] = track.index data["title"] = track.title data["artist"] = track.artist data["album"] = track.album data["progress"] = int(round(pctl.playing_time * 1000)) data["track"] = self.get_track(0, 0, track) p = pctl.playlist_playing_position data["position"] = p data["album_id"] = 0 playlist = pctl.playing_playlist() if p < len(playlist): while True: if p < 0 or pctl.g( playlist[p] ).parent_folder_path != track.parent_folder_path: p += 1 break p -= 1 data["album_id"] = p data = json.dumps(data).encode() self.wfile.write(data) else: self.send_response(404) self.end_headers() self.wfile.write(b"404 Not found") class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): pass try: httpd = ThreadedHTTPServer(("0.0.0.0", 7814), Server) httpd.serve_forever() httpd.server_close() except OSError: print("Not starting web api server, already running?")