Example #1
0
 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})
Example #2
0
 def _process_read_queue(self):
     """Spawns a pool of worker processes to handle reading the metadata
     from all of the files in the read_queue.
     """
     self.reading.set()
     status = 'Importing songs to the library'
     self.jobs[self.current_job]['status'] = status
     # Create a pool of processes to handle the actual reading of tags.
     # Using processes instead of threads lets us take full advantage of
     # multi-core CPUs so this operation doesn't take as long.
     self.pool = Pool()
     queue_size = self.read_queue.qsize()
     while self.read_queue.qsize() > 0 and not self.stopping.is_set():
         args_list = []
         # Get 100 songs from the queue
         for x in range(100):
             try:
                 args_list.append(self.read_queue.get(timeout=1))
             except:
                 break
         # Read the tags from the 100 songs and then stick the results in
         # the database queue.
         try:
             self.db_queue.put(self.pool.map(read_metadata, args_list))
             n_items = len(args_list)
             self.jobs[self.current_job]['processed_items'] += n_items
         except:
             logger.error("Error processing read queue.")
         logger.debug("Processed %d items in %0.2f seconds" %
                      (queue_size - self.read_queue.qsize(),
                      time() - self.start_time))
     self.pool.close()
     self.pool.join()
     self.reading.clear()
Example #3
0
 def __init__(self, db_url=cfg['COUCHDB_URL'],
               db_username=cfg['COUCHDB_USER'],
               db_password=cfg['COUCHDB_PASSWORD']):
     """Sets up the database connection and starts loading songs."""
     # Initiate a connection to the database server
     self.shutting_down = threading.Event()
     self.building_cache = threading.Event()
     logger.debug("Initiating the database connection.")
     filters = []
     if db_username or db_password:
         filters.append(BasicAuth(db_username, db_password))
     self._server = Server(db_url, filters=filters)
     connect_retries = 0
     while True:
         try:
             # Get a reference to our database
             self.db = self._server.get_or_create_db(cfg['DATABASE_NAME'])
             break
         except Exception as e:
             logger.error(str(e))
             if connect_retries < 3:
                 logger.error("Error connecting to CouchDB.")
                 connect_retries += 1
                 sleep(3)
             else:
                 logger.error("Could not connect to CouchDB. Quitting.")
                 sys.exit(1)
     logger.debug("Loading database design documents.")
     # Load our database views from the filesystem
     loader = FileSystemDocsLoader(os.path.join(cfg['ASSETS_DIR'],
                                   'views/_design'))
     try:
         loader.sync(self.db, verbose=True)
     except:
         pass
     logger.debug("Initializing the database cache.")
     self.cache = BlofeldCache(self.db)
     self.scanner = Scanner(cfg['MUSIC_PATH'], self.db)
     self.update()
Example #4
0
    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)
Example #5
0
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()
Example #6
0
def read_metadata((location, id, mtime, revision)):
    """Read the metadata of a file and return it as a dict."""
    # Try to open the file and read its tags
    try:
        metadata = mutagen.File(location, None, True)
    except:
        logger.error("%s made Mutagen explode." % location)
        return None
    # Create the metadata object we're going to return with some default
    # values filled in. This is just in case there aren't tags for these
    # things so we don't run into problems elsewhere with this data not
    # being there.
    song = {
        '_id': id,
        'location': location,
        'title': os.path.split(location)[1],
        'mtime': mtime,
        'artist': 'Unknown Artist',
        'album': 'Unknown Album',
        'genre': [],
        'date': '',
        'tracknumber': 0,
        'discnumber': 1,
        'bitrate': 0,
        'length': 0,
        'subtitle': '',
        'discsubtitle': '',
        'compilation': False,
        'replaygain_track_gain': None,
        'replaygain_album_gain': None,
        'replaygain_track_peak': None,
        'replaygain_album_peak': None,
        'type': 'song'
    }
    if revision:
        song['_rev'] = revision
    # Try to set the length of the song in seconds, the bitrate in bps and
    # the mimetype and a default track number of the file.
    try:
        song['length'] = metadata.info.length
    except:
        pass
    try:
        song['bitrate'] = metadata.info.bitrate
    except:
        pass
    try:
        song['mimetype'] = metadata.mime[0]
    except:
        pass
    # Now we parse all the metadata and transfer it to our song object.
    if os.path.splitext(location)[1].lower()[1:] in ('wma', 'asf'):
        song = parse_wma(song, metadata)
    else:
        song = parse_metadata(song, metadata)
    # Fill in some needed tags if they're not present
    if not song.has_key('albumartist'):
        song['albumartist'] = song['artist']
    for field in ('title', 'artist', 'album', 'albumartist'):
        if not song.has_key('%ssort' % field):
            song['%ssort' % field] = song[field]
    # Create artist and album IDs which are just SHA-1 hashes of each field
    for field in ('artist', 'album'):
        value = song[field].encode('utf-8')
        song['%s_hash' % field] = hashlib.sha1(value).hexdigest()
    return song