Ejemplo n.º 1
0
    def scan_file(self, item, file):
        """Try to open file with mutagen to extract information

        :param item: MPD uri descriptor (path without local music path)
        :param file: full file name
        :return: (uri, title, artist, album, genre, date, rating, original, time-dummy, size-dummy)
        """
        write_gpio_pipe('2 flash 0.2')

        try:
            m = mutagen.File(file)
        except HeaderNotFoundError as e:
            # (re)try to open by extension
            ext = os.path.splitext(file)[-1].lower()
            try:
                if ext == '.mp3':
                    m = mutagen.mp3.MP3(file)
                elif ext == '.ogg':
                    m = mutagen.oggvorbis.OggVorbis(file)
                elif ext == '.mpc':
                    m = mutagen.musepack.Musepack(file)
                elif ext == 'm4a':
                    m = mutagen.mp4.MP4(file)
                elif ext == 'flac':
                    m = mutagen.flac.FLAC(file)
                elif ext == 'wma':
                    m = mutagen.asf.ASF(file)
                else:
                    self.main.print_message(file)
                    self.main.print_message('MUTAGEN ERROR {0}'.format(e))
                    m = None
            except HeaderNotFoundError as e:
                self.main.print_message(file)
                self.main.print_message('MUTAGEN ERROR 2 {0}'.format(e))
                m = None
        except (FileNotFoundError, mutagen.MutagenError):
            self.main.print_message('File not found: ' + file)
            return None

        if m is None:
            self.main.print_message('NO TAGS FOR ' + file)
            return item, self.plain_filename(
                file), '', '', '', 0, 0, True, 0, 0,
        elif type(m) == mutagen.mp3.MP3:
            return self.parse_mp3(item, m, file)
        elif type(m) == mutagen.oggvorbis.OggVorbis:
            return self.parse_ogg(item, m, file)
        elif type(m) == mutagen.musepack.Musepack:
            return self.parse_mpc(item, m, file)
        elif type(m) == mutagen.flac.FLAC:
            return self.parse_ogg(item, m, file)
        elif type(m) == mutagen.mp4.MP4:
            return self.parse_m4a(item, m, file)
        elif type(m) == mutagen.asf.ASF:
            return self.parse_wma(item, m, file)

        self.main.print_message('UNKNOWN TYPE: ' + file)
        self.main.print_message(file)
        self.main.print_message(m)
        return item, self.plain_filename(file), '', '', '', 0, 0, True, 0, 0,
Ejemplo n.º 2
0
    def mpc_idle(self):
        """Idle until event received from MPD.
        Note: this is blocking. To break this loop, toggle some switch on MPD.
        """
        self.ensure_connected()
        res = ['']
        if self.connected:
            res = self.client.idle()
            self.check_party_mode(res)

        if 'update' in res and 'updating_db' not in self.client.status():
            # let ratings scanner do rescan if database updated
            self.main.rescan_ratings = True
            # clear yellow LED
            write_gpio_pipe('1 0')
        elif 'update' in res:
            # raise yellow LED
            write_gpio_pipe('1 1')

        if 'playlist' in res:
            # Tell playlist view to update its status.
            redis_publisher = RedisPublisher(facility='piremote',
                                             broadcast=True)
            redis_publisher.publish_message(RedisMessage('playlist'))

        if 'player' not in res:
            return

        # Publish current player state via websocket via redis broadcast.
        state = self.client.status()
        cur = self.client.currentsong()
        state_data = dict(event=res)
        msg = json.dumps(state_data)
        redis_publisher = RedisPublisher(facility='piremote', broadcast=True)
        redis_publisher.publish_message(RedisMessage(msg))

        # Check if playing and file changed --> insert into history if so.
        if 'state' in state and state['state'] == 'play':
            file = cur['file']
            if file != self.last_file:
                self.last_file = file
                if 'artist' in cur and 'title' in cur:
                    title = '%s - %s' % (cur['artist'], cur['title'])
                elif 'title' in cur:
                    title = cur['title']
                else:
                    no_ext = os.path.splitext(file)[0]
                    title = os.path.basename(no_ext).replace('_', ' ')
                self.insert_into_history(file, title)
Ejemplo n.º 3
0
    def do_upload(self, filename):
        """Check if file can be uploaded and perform upload.

        Remove leading entry from PB_UPLOAD_SOURCES settings from filename.
        Add PB_UPLOAD_DIR to destination file name.

        :param filename: full path of file to be uploaded.
        :return: True if file uploaded
        """

        self.main.print_message('Uploading %s' % filename)

        # Check if
        src_size = os.path.getsize(filename)
        s = os.statvfs(self.upload_path)
        free = s.f_bavail * s.f_frsize
        # keep at least 200MB (logs, cache, whatever)
        if src_size > free - 1024 * 1024 * 200:
            self.main.print_message('DRIVE FULL, NOT UPLOADING')
            return False

        name = filename

        # get pure filename (without prefix)
        for src in self.upload_sources:
            if filename.startswith(src):
                name = filename.replace(src, '')

        if name.startswith('/'):
            name = name[1:]

        dest = os.path.join(self.upload_path, name)

        if os.path.isfile(dest):
            if os.path.getsize(dest) == src_size:
                # File exists - no upload
                return False

        # check dir exists
        dirname = os.path.dirname(dest)
        if not os.path.isdir(dirname):
            try:
                os.makedirs(dirname)
            except OSError as e:
                self.main.print_message('MKDIR FAILED: ' + dirname)
                self.main.print_message("OSError: {0}".format(e))
                return False

        write_gpio_pipe('2 1')  # raise red led

        # perform copy
        try:
            shutil.copy(filename, dest)
        except IOError as e:
            self.main.print_message('COPY FAILED: ' + dest)
            self.main.print_message("IOError: {0}".format(e))
            return False

        write_gpio_pipe('2 0')  # clear red led

        return True
Ejemplo n.º 4
0
    def rescan(self):
        """Check if new items in MPD database which are not in SQL database.

        Read tags and append to database if so.
        Remove items from SQL database which are no longer in MPD database.

        Called via main daemon loop, triggered via MPD_Idler.
        Does not block too much, will leave scanning loop if keep_run is False in main.
        """
        self.main.print_message("RESCANNING RATINGS")
        rescan_broken = False  # set to True if add chunk is too large

        if len(self.to_add) == 0 and len(self.to_remove) == 0:

            write_gpio_pipe("1 1\n3 1")  # raise yellow and blue led

            mpd_files = self.get_mpd_files()
            if mpd_files is None or len(mpd_files) == 0:
                # There was a mpd connect error, retry next loop.
                # Or: the music db was completely wiped out. Do nothing until there are files again.
                return

            # Get mpd files which are not in database
            not_in_db = self.get_db_files(not_in_database=mpd_files)
            if not_in_db is None:
                # there was an db read error, retry next loop
                return

            music_path = self.get_music_path()
            if music_path is None:
                # there was an mpd error, retry next loop
                return
            self.to_add = []
            for item in not_in_db:
                filename = os.path.join(music_path, item)
                mp3_info = self.scan_file(item, filename)
                if mp3_info is not None:
                    self.to_add.append(mp3_info)
                if not self.main.keep_run:
                    # worker shut down in the meantime
                    return
                if len(self.to_add) > 499:
                    # add in chunks of 500
                    rescan_broken = True
                    break

            too_many = self.get_db_files(not_in_list=mpd_files)
            self.to_remove = []
            for item in too_many:
                self.to_remove.append((item, ))

            if not self.main.keep_run:
                # worker shut down in the meantime
                return

            if DEBUG:
                self.main.print_message('FOUND %d new files in mpd db' %
                                        len(self.to_add))
                self.main.print_message(
                    'FOUND %d files in db which are not in mpd db' %
                    len(self.to_remove))

            write_gpio_pipe("1 0\n3 0")  # clear yellow and blue led

            # Files to_add scanned and to_remove found #

        if len(self.to_add) > 0 and False:  # does not work for large DBs.
            # read times from MPD database for new items
            to_add_times = []
            write_gpio_pipe("1 1")  # raise yellow led
            client = MPDClient()
            client.timeout = 10
            try:
                client.connect('localhost', 6600)
            except ConnectionError:
                self.main.print_message("ERROR: CONNECT")
                return  # retry next loop
            for add_item in self.to_add:
                if not self.main.keep_run:
                    # worker shut down in the meantime
                    return
                add = list(add_item)
                try:
                    mpd_item = client.find('file', add_item[0])
                except (ConnectionError, CommandError):
                    self.main.print_message("ERROR: CONNECT")
                    return  # retry next loop
                if len(mpd_item) == 1 and 'time' in mpd_item[0]:
                    try:
                        length = int(mpd_item[0]['time'])
                    except ValueError:
                        length = 0
                    add[8] = length
                to_add_times.append(add)
            self.to_add = to_add_times
            write_gpio_pipe("1 0")  # clear yellow led

            # times scanned from MPD #

        if len(self.to_add) > 0 or len(self.to_remove) > 0:
            # apply to SQL
            write_gpio_pipe("3 1")  # raise blue led
            self.alter_db('insert_many', self.to_add)
            self.alter_db('remove_many', self.to_remove)
            write_gpio_pipe("3 0")  # clear blue led

        self.main.rescan_ratings = rescan_broken  # do not rerun on successful run
        self.to_add = []  # required to not block rating scanner for next run
        self.to_remove = []
Ejemplo n.º 5
0
    def check_party_mode(self, res, force=False):
        """Check if party mode is on, append items to playlist/shrink playlist if needed.

        Fetch settings directly from SQL database of piremote App.
        This is some of the dirtiest hacks possible for process communication,
        but works for this purpose.

        DB settings:
        party_mode: ['0', '1'] '1' if use party mode -> auto extend playlist
        party_remain: 'int' number of songs to keep in playlist above current song.
        party_low_water: 'int' if this number of songs left, append.
        party_high_water: 'int' when appending, append until this number of songs left.
        """

        playlist_event = force
        for ev in res:
            if ev == 'player' or ev == 'playlist':
                playlist_event = True

        if not playlist_event:
            return

        # Fetch from database.
        # Note: this is how IPC is performed here -- not nice, but works.
        party_mode = False
        party_low_water = 10
        party_high_water = 20
        party_remain = 10

        db = DATABASES['default']
        conn = psycopg2.connect(dbname=db['NAME'],
                                user=db['USER'],
                                password=db['PASSWORD'],
                                host=db['HOST'])
        cur = conn.cursor()
        cur.execute('''SELECT key, value FROM piremote_setting''')
        for row in cur.fetchall():
            if row[0] == 'party_mode':
                party_mode = row[1] == '1'
            if row[0] == 'party_remain':
                party_remain = int(row[1])
            if row[0] == 'party_low_water':
                party_low_water = int(row[1])
            if row[0] == 'party_high_water':
                party_high_water = int(row[1])
        cur.close()
        conn.close()

        if not party_mode:
            return

        status = self.client.status()

        pos = int(status['song']) if 'song' in status else 0
        pl_len = int(status['playlistlength'])
        pl_remain = max(pl_len - pos - 1, 0)

        # Append randomly until high_water mark reached, if below low water mark.
        if pl_remain < party_low_water:
            write_gpio_pipe("4 1")  # raise white led
            pl_add = max(party_high_water - pl_remain, 0)
            db_files = self.client.list('file')
            for i in range(pl_add):
                self.client.add(db_files[random.randrange(0, len(db_files))])
            write_gpio_pipe("4 0")  # clear white led

        # Shrink playlist until 'remain' songs left before current item.
        if pos > party_remain:
            for i in range(pos - party_remain):
                self.client.delete(0)