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 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 _update(self): self.start_time = time() # Clean the database of files that no longer exist and get a list # of the remaining songs in the database. self._clean() # Create a queue of files from which we need to read metadata. self._scan() if self.read_queue.qsize() > 0: # Spawn a new thread to handle the queue of files that need read. self.read_thread = Thread(target=self._process_read_queue) self.read_thread.start() # Give the read_thread a chance to get going and then spawn a # new thread to handle inserting metadata into the database. sleep(5) self.db_thread = Thread(target=self._add_items_to_db) self.db_thread.start() # Block while we wait for everything to finish self.db_working.wait(None) # Join our threads back self.read_thread.join() self.db_thread.join() self.jobs[self.current_job]['current_item'] = 'None' # Compact the database so it doesn't get unreasonably large. We do # this in a separate thread so we can go ahead and return since the # we've added everything we need to the database already and we # don't want to wait for this to finish. self.compact_thread = Thread(target=self._compact) self.compact_thread.start() self._compact() self.updating.clear() self.finished.set() finish_time = time() - self.start_time self.jobs[self.current_job]['status'] = 'Finished' self.jobs[self.current_job]['total_time'] = finish_time logger.debug("Added all new songs in %0.2f seconds." % finish_time) logger.info("Updated library in %0.2f seconds." % finish_time)
def get_song(self, songid=None, format=False, bitrate=False): logger.debug("%s (%s)\tget_song(songid=%s, format=%s, bitrate=%s)\tHeaders: %s" % (utils.find_originating_host(cherrypy.request.headers), cherrypy.request.login, songid, format, bitrate, cherrypy.request.headers)) log_message = "%s (%s) is listening to " % (cherrypy.request.login, utils.find_originating_host(cherrypy.request.headers)) last = self.multi_requests.get(songid, None) show_log = False if not last or (last and time.time() > (last + 30)): show_log = True self.multi_requests[songid] = time.time() try: range_request = cherrypy.request.headers['Range'] except: range_request = "bytes=0-" try: song = self.library.db[songid] path = song['location'] except: log_message += "a song ID which could not be found: %s" % str(songid) logger.error(log_message) raise cherrypy.HTTPError(404) log_message += '"%s" by %s from %s ' % (song['title'].encode(cfg['ENCODING']), song['artist'].encode(cfg['ENCODING']), song['album'].encode(cfg['ENCODING'])) try: client_os, client_browser = httpagentparser.simple_detect(cherrypy.request.headers['User-Agent']) # b = self.bc(cherrypy.request.headers['User-Agent']) # if b: # browser = "%s %s.%s on %s" % (b.name(), b.version()[0], b.version()[1], b.get("platform")) # else: # browser = cherrypy.request.headers['User-Agent'] # log_message += "using %s." % browser except: client_os = 'an OS' client_browser = 'a browser' try: if bitrate: bitrate = str(bitrate) force_transcode = False if bitrate and \ (int(bitrate) in [8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]) and \ (song['bitrate'] / 1024 > int(bitrate)): force_transcode = True except: pass try: song_format = [song['mimetype'].split('/')[1], os.path.splitext(path)[1].lower()[1:]] except: song_format = [os.path.splitext(path)[1].lower()[1:]] if True in [True for x in song_format if x in ['mp3']]: song_mime = 'audio/mpeg' song_format = ['mp3'] elif True in [True for x in song_format if x in ['ogg', 'vorbis', 'oga']]: song_mime = 'audio/ogg' song_format = ['ogg', 'vorbis', 'oga'] elif True in [True for x in song_format if x in ['m4a', 'aac', 'mp4']]: song_mime = 'audio/x-m4a' song_format = ['m4a', 'aac', 'mp4'] else: song_mime = 'application/octet-stream' if not (format or bitrate): #log_message += " The client did not request any specific format or bitrate so the file is being sent as-is (%s kbps %s)." % (str(song['bitrate'] / 1000), str(song_format)) log_message += "(%skbps %s)" % (str(song['bitrate']), song_format[0]) if client_os and client_browser: log_message += " using %s on %s." % (client_browser, client_os) else: log_message += "." logger.info(log_message) if not os.name == 'nt': path = path.encode(cfg['ENCODING']) return serve_file(path, song_mime, "inline", os.path.split(path)[1]) if format: format = str(format).split(',') else: format = song_format logger.debug("The client wants %s and the file is %s" % (format, song_format)) if True in [True for x in format if x in song_format] and not force_transcode: #if bitrate: # log_message += " The client requested %s kbps %s, but the file is already %s kbps %s, so the file is being sent as-is." % (bitrate, format, str(song['bitrate'] / 1000), str(song_format)) #else: # log_message += " The client requested %s, but the file is already %s, so the file is being sent as-is." % (format, str(song_format)) log_message += "(%skbps %s)" % (str(song['bitrate'] / 1000), song_format[0]) if client_os and client_browser: log_message += " using %s on %s." % (client_browser, client_os) else: log_message += "." if show_log: logger.info(log_message) if not os.name == 'nt': path = path.encode(cfg['ENCODING']) return serve_file(path, song_mime, "inline", os.path.split(path)[1]) else: #if bitrate: # log_message = " The client requested %s kbps %s, but the file is %s kbps %s, so we're transcoding the file for them." % (bitrate, format, str(song['bitrate'] / 1000), str(song_format)) #else: # log_message += " The client requested %s, but the file %s, so we're transcoding the file for them." % (format, str(song_format)) log_message += "(transcoded from %skbps %s to %skbps %s)" % (str(song['bitrate'] / 1000), song_format[0], str(bitrate), format[0]) if client_os and client_browser: log_message += " using %s on %s." % (client_browser, client_os) else: log_message += "." if show_log: logger.info(log_message) # If we're transcoding audio and the client is trying to make range # requests, we have to throw an error 416. This sucks because it breaks # <audio> in all the WebKit browsers I've tried, but at least it stops # them from spawning a zillion transcoder threads (I'm looking at you, # Chromium). if True in [True for x in format if x in ['mp3']]: # cherrypy.response.headers['Content-Length'] = '-1' if range_request != 'bytes=0-': logger.debug("Got a range request for a file that needs transcoded: %s" % range_request) raise cherrypy.HTTPError(416) else: cherrypy.response.headers['Content-Type'] = 'audio/mpeg' try: if cherrypy.request.headers['Referer'].lower().endswith('jplayer.swf'): cherrypy.response.headers['Content-Type'] = 'audio/mp3' except: pass #cherrypy.response.headers['Content-Type'] = 'application/octet-stream' return self.transcoder.transcode(path, 'mp3', bitrate) elif True in [True for x in format if x in ['ogg', 'vorbis', 'oga']]: # cherrypy.response.headers['Content-Length'] = '-1' if range_request != 'bytes=0-': logger.debug("Got a range request for a file that needs transcoded: %s" % range_request) raise cherrypy.HTTPError(416) else: cherrypy.response.headers['Content-Type'] = 'audio/ogg' #cherrypy.response.headers['Content-Type'] = 'application/octet-stream' return self.transcoder.transcode(path, 'ogg', bitrate) elif True in [True for x in format if x in ['m4a', 'aac', 'mp4']]: # cherrypy.response.headers['Content-Length'] = '-1' if range_request != 'bytes=0-': logger.debug("Got a range request for a file that needs transcoded: %s" % range_request) raise cherrypy.HTTPError(416) else: cherrypy.response.headers['Content-Type'] = 'audio/x-m4a' #cherrypy.response.headers['Content-Type'] = 'application/octet-stream' return self.transcoder.transcode(path, 'm4a', bitrate) else: raise cherrypy.HTTPError(501)
def stop(self): logger.info("Preventing new library calls.") self.shutting_down.set() self.scanner.stop()
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()