Example #1
0
 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.")
Example #2
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 #3
0
 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()
Example #4
0
 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
Example #5
0
 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))
Example #6
0
 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()
Example #7
0
 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()
Example #8
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 #9
0
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