def download(self, songs=None, artists=None ,albums=None, query=None): logger.debug("%s (%s)\tdownload(songs=%s, artists=%s, albums=%s, query=%s)\tHeaders: %s" % (utils.find_originating_host(cherrypy.request.headers), cherrypy.request.login, songs, artists, albums, query, cherrypy.request.headers)) if cfg['REQUIRE_LOGIN'] and cherrypy.request.login not in cfg['GROUPS']['download']: logger.warn("%(user)s (%(ip)s) requested a download, but was denied because %(user)s is not a member of the download group." % {'user': cherrypy.request.login, 'ip': utils.find_originating_host(cherrypy.request.headers)}) raise cherrypy.HTTPError(401,'Not Authorized') file_list = [] if not songs and not artists and not albums and not query: raise cherrypy.HTTPError(501) elif songs: songs = songs.split(',') if len(songs) > 100: return "Too many songs! Please narrow your criteria." for song in songs: try: file_list.append(self.library.db[song]) except: raise cherrypy.HTTPError(404,'Not Found') else: if artists: artists = artists.split(',') if albums: albums = albums.split(',') files = self.library.songs(artists, albums, query) if len(files) > 100: return "Too many songs! Please narrow your criteria." for song in files: file_list.append(self.library.db[song['id']]) archive = create_archive(file_list) try: return serve_file(archive, 'application/zip', 'download.zip') except: logger.debug("Something went wrong while sending the archive.")
def config(self, set_option=None, get_option=None, value=None): logger.debug("%s (%s)\tconfig()\tHeaders: %s" % (utils.find_originating_host(cherrypy.request.headers), cherrypy.request.login, cherrypy.request.headers)) if cfg['REQUIRE_LOGIN'] and cherrypy.request.login not in cfg['GROUPS']['admin']: logger.warn("%(user)s (%(ip)s) requested configuration data, but was denied because %(user)s is not a member of the admin group." % {'user': cherrypy.request.login, 'ip': utils.find_originating_host(cherrypy.request.headers)}) raise cherrypy.HTTPError(401,'Not Authorized') if set_option: if not value: raise cherrypy.HTTPError(501,'No value provided for the requested option') if set_option not in cfg.keys(): raise cherrypy.HTTPError(501,'The requested option does not exist') try: if type(cfg[set_option]) is types.ListType: value = value.split(',') if type(cfg[set_option]) is types.BooleanType: value = json.loads(value) if type(cfg[set_option]) is types.StringType: value = str(value) except: raise cherrypy.HTTPError(501, 'The value provided was the wrong type. Expected a %s' % type(cfg[set_option])) try: cfg[set_option] = value cfg.save_config() cherrypy.response.headers['Content-Type'] = 'application/json' return json.dumps({'config': {set_option: cfg[set_option]}}) except Exception as x: logger.error("Could not save configuration. The error was: %s" % str(x)) if get_option: if get_option not in cfg.keys(): raise cherrypy.HTTPError(501,'The requested option does not exist') cherrypy.response.headers['Content-Type'] = 'application/json' return json.dumps({'config': {get_option: cfg[get_option]}}) cherrypy.response.headers['Content-Type'] = 'application/json' return json.dumps({'config': cfg})
def transcode(self, path, format='mp3', bitrate=False): if self.stopping.is_set(): return try: stop = Event() start_time = time.time() parent_conn, child_conn = Pipe() process = Process(target=transcode_process, args=(child_conn, path, stop, format, bitrate)) process.start() while not (self.stopping.is_set() or stop.is_set()): data = parent_conn.recv() if not data: break yield data logger.debug("Transcoded %s in %0.2f seconds." % (path.encode(cfg['ENCODING']), time.time() - start_time)) except GeneratorExit: stop.set() logger.debug("User canceled the request during transcoding.") except: stop.set() logger.warn("Some type of error occured during transcoding.") finally: parent_conn.close() process.join()
def update(self): """Initiates the process of updating the database by removing any songs that have gone missing, adding new songs and updating changed songs. """ if self.updating.is_set(): logger.warn("Library update requested, but already updating.") return self.current_job self.updating.set() self.update_thread = Thread(target=self._update) ticket = hashlib.sha1(str(time())).hexdigest() self.jobs[ticket] = { 'status': 'Initializing', 'current_item': self.music_path, 'removed_items': 0, 'new_items': 0, 'changed_items': 0, 'unchanged_items': 0, 'queued_items': 0, 'processed_items': 0, 'total_time': 0 } self.current_job = ticket logger.info("Starting library update.") self.update_thread.start() return ticket
def update_library(self, ticket=None): #logger.debug("%s (%s)\tupdate()\tHeaders: %s" % (utils.find_originating_host(cherrypy.request.headers), cherrypy.request.login, cherrypy.request.headers)) if cfg['REQUIRE_LOGIN'] and cherrypy.request.login not in cfg['GROUPS']['admin']: logger.warn("%(user)s (%(ip)s) requested a library update, but was denied because %(user)s is not a member of the admin group." % {'user': cherrypy.request.login, 'ip': utils.find_originating_host(cherrypy.request.headers)}) raise cherrypy.HTTPError(401,'Not Authorized') cherrypy.response.headers['Content-Type'] = 'application/json' if not ticket: return json.dumps({"ticket": self.library.update()}) else: return json.dumps(self.library.update(ticket=ticket))
def flush_db(self): logger.debug("%s (%s)\tshutdown()\tHeaders: %s" % (utils.find_originating_host(cherrypy.request.headers), cherrypy.request.login, cherrypy.request.headers)) if cfg['REQUIRE_LOGIN'] and cherrypy.request.login not in cfg['GROUPS']['admin']: logger.warn("%(user)s (%(ip)s) requested that the database be flushed, but was denied because %(user)s is not a member of the admin group." % {'user': cherrypy.request.login, 'ip': utils.find_originating_host(cherrypy.request.headers)}) raise cherrypy.HTTPError(401,'Not Authorized') try: cherrypy.response.headers['Content-Type'] = 'application/json' logger.info("Received flush database request, complying.") return json.dumps({'flush_db': True}) except: pass finally: self.library.db.flush()
def shutdown(self): logger.debug("%s (%s)\tshutdown()\tHeaders: %s" % (utils.find_originating_host(cherrypy.request.headers), cherrypy.request.login, cherrypy.request.headers)) if cfg['REQUIRE_LOGIN'] and cherrypy.request.login not in cfg['GROUPS']['admin']: logger.warn("%(user)s (%(ip)s) requested that the server shut down, but was denied because %(user)s is not a member of the admin group." % {'user': cherrypy.request.login, 'ip': utils.find_originating_host(cherrypy.request.headers)}) raise cherrypy.HTTPError(401,'Not Authorized') try: cherrypy.response.headers['Content-Type'] = 'application/json' logger.info("Received shutdown request, complying.") self.transcoder.stop() self.library.scanner.stop() return json.dumps({'shutdown': True}) except: pass finally: shutting_down.set() logger.debug("Stopping CherryPy.") cherrypy.engine.exit()
def transcode_process(conn, path, stop, format='mp3', bitrate=False): import gi gi.require_version('Gst', '1.0') from gi.repository import GObject, Gst GObject.threads_init() Gst.init(None) # If we were passed a bitrate argument, make sure it's actually a number try: bitrate = int(bitrate) except: bitrate = False log_message = "Transcoding %s to %s" % (path.encode(cfg['ENCODING']), format) # Create our transcoding pipeline using one of the strings at the end of # this module. transcoder = Gst.parse_launch(pipeline[format]) # Set the bitrate we were asked for if bitrate in [8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]: log_message += " at %d kbps" % bitrate encoder = transcoder.get_by_name('encoder') muxer = transcoder.get_by_name('muxer') if format is 'mp3': encoder.set_property("target", "bitrate") encoder.set_property("bitrate", bitrate) elif format is 'ogg': encoder.set_property("max-bitrate", bitrate * 1024) elif format is 'm4a': encoder.set_property("bitrate", bitrate * 1000) #encoder.set_property("outputformat", 1) #encoder.set_property("profile", 1) #muxer.set_property("faststart", True) # Load our file into the transcoder log_message += "." logger.info(log_message) source = transcoder.get_by_name('source') try: source.set_property("location", path.encode('utf-8')) # Set the output to be asynchronous so the transcoding happens as quickly # as possible rather than real time. output = transcoder.get_by_name('output') output.set_property("sync", False) # Start the pipeline running so we can start grabbing data out of it transcoder.set_state(Gst.State.PLAYING) #transcoder.get_state() try: # Grab a bit of encoded data and yield it to the client while True: sample = output.emit('pull-sample') if sample and not stop.is_set(): buf = sample.get_buffer() data = buf.extract_dup(0, buf.get_size()) conn.send(data) else: break except Exception as e: logger.warn(str(e)) logger.warn("Some type of error occured during transcoding.") except Exception as e: logger.error(str(e)) logger.error("Could not open the file for transcoding. This is probably happening because there are non-ASCII characters in the filename.") finally: try: # I think this is supposed to free the memory used by the transcoder transcoder.set_state(Gst.State.NULL) except: pass if not stop.is_set(): conn.send(False) conn.close()
def parse_wma(song, metadata): """Parses a Mutagen WMA metadata object and transfers that data to a song object using a dict to map the WMA tag names to ones that we want them to be. This is a specialized version of parse_metadata(). """ for tag, value in metadata.iteritems(): try: # Mutagen returns all metadata as lists, so normally we'd just # get value[0], but 'genre' and 'mood' get special treatment # because we want to save a list to the database. That way we # can support multiple genre/mood tags. if tag in ('WM/Genre', 'WM/Mood'): song[asf_map[tag]] = [] for genre in value: song[asf_map[tag]].append(genre.value) # Sometimes tracknumbers are packed along with the totaltracks # using a '/', e.g. 2/11 and sometimes it's just the tracknumber. # We need to account for both cases. elif tag == 'WM/TrackNumber': try: tracknumber, totaltracks = str(value[0].value).split('/') song['tracknumber'] = int(tracknumber) song['totaltracks'] = int(totaltracks) song['tracktotal'] = int(totaltracks) except: song['tracknumber'] = int(value[0].value) # Disc numbers work the same way as track numbers. elif tag == 'WM/PartOfSet': try: discnumber, totaldiscs = str(value[0].value).split('/') song['discnumber'] = int(discnumber) song['totaldiscs'] = int(totaldiscs) song['disctotal'] = int(totaldiscs) except: song['discnumber'] = int(value[0].value) elif tag == 'TotalTracks': song['tracktotal'] = int(value[0].value) song['totaltracks'] = int(value[0].value) elif tag in ('foobar2000/TOTALDISCS', 'TotalDiscs'): song['disctotal'] = int(value[0].value) song['totaldiscs'] = int(value[0].value) elif tag == 'WM/IsCompilation': song['compilation'] = bool(value[0]) elif tag in ['replaygain_album_peak', 'replaygain_track_peak']: song[tag] = float(value[0]) # We need to ignore tags that contain cover art data so we don't # end up inserting giant binary blobs into our database. elif tag == 'WM/Picture': continue else: if type(value[0]) is unicode: song[asf_map[tag]] = value[0] else: song[asf_map[tag]] = value[0].value except TypeError: pass except AttributeError: pass except KeyError: # We encountered a tag that wasn't in asf_map. Print it out to # the console so that hopefully someone will tell us and we can # add it. try: logger.warn("Skipping unrecognized tag %s with data %s in \ file %s" % (tag, value[0], song['location'])) except: logger.warn("Skipping unrecognized tag %s with un-printable \ data in file %s" % (tag, song['location'])) if not song.has_key('albumartist'): song['albumartist'] = song['artist'] return song