コード例 #1
0
    def test_subcommand_query_exclude(self):
        item = self.add_item_fixture(year=2016,
                                     day=13,
                                     month=3,
                                     comments=u'test comment')

        item.write()

        self.config['zero']['fields'] = ['comments']
        self.config['zero']['update_database'] = False
        self.config['zero']['auto'] = False

        self.load_plugins('zero')
        self.run_command('zero', 'year: 0000')

        mf = MediaFile(syspath(item.path))

        self.assertEqual(mf.year, 2016)
        self.assertEqual(mf.comments, u'test comment')
コード例 #2
0
    def test_write_date_components(self):
        mediafile = self._mediafile_fixture('full')
        mediafile.year = 2001
        mediafile.month = 1
        mediafile.day = 2
        mediafile.original_year = 1999
        mediafile.original_month = 12
        mediafile.original_day = 30
        mediafile.save()

        mediafile = MediaFile(mediafile.path)
        self.assertEqual(mediafile.year, 2001)
        self.assertEqual(mediafile.month, 1)
        self.assertEqual(mediafile.day, 2)
        self.assertEqual(mediafile.date, datetime.date(2001,1,2))
        self.assertEqual(mediafile.original_year, 1999)
        self.assertEqual(mediafile.original_month, 12)
        self.assertEqual(mediafile.original_day, 30)
        self.assertEqual(mediafile.original_date, datetime.date(1999,12,30))
コード例 #3
0
    def test_no_patterns(self):
        self.config['zero']['fields'] = ['comments', 'month']

        item = self.add_item_fixture(
            comments=u'test comment',
            title=u'Title',
            month=1,
            year=2000,
        )
        item.write()

        self.load_plugins('zero')
        item.write()

        mf = MediaFile(syspath(item.path))
        self.assertIsNone(mf.comments)
        self.assertIsNone(mf.month)
        self.assertEqual(mf.title, u'Title')
        self.assertEqual(mf.year, 2000)
コード例 #4
0
ファイル: test_mediafile.py プロジェクト: tux-00/beets
    def test_add_tiff_image(self):
        mediafile = self._mediafile_fixture('image')
        self.assertEqual(len(mediafile.images), 2)

        image = Image(data=self.tiff_data,
                      desc=u'the composer',
                      type=ImageType.composer)
        mediafile.images += [image]
        mediafile.save()

        mediafile = MediaFile(mediafile.path)
        self.assertEqual(len(mediafile.images), 3)

        # WMA does not preserve the order, so we have to work around this
        image = filter(lambda i: i.mime_type == 'image/tiff',
                       mediafile.images)[0]
        self.assertExtendedImageAttributes(image,
                                           desc=u'the composer',
                                           type=ImageType.composer)
コード例 #5
0
ファイル: __init__.py プロジェクト: drizzt/beets-replaygain
    def album_imported(self, lib, album):
        self.write_album = True

        print_("Tagging Replay Gain:  %s - %s" %
               (album.albumartist, album.album))

        try:
            media_files = [MediaFile(item.path) for item in album.items()]
            media_files = [mf for mf in media_files if self.requires_gain(mf)]

            #calculate gain. Return value - track_data: array dictionary indexed by filename
            track_data, album_data = rgcalc.calculate(
                [mf.path for mf in media_files], True, self.ref_level)

            for mf in media_files:
                self.write_gain(mf, track_data, album_data)

        except (FileTypeError, UnreadableFileError, TypeError, ValueError), e:
            log.error("failed to calculate replaygain:  %s ", e)
コード例 #6
0
ファイル: test_info.py プロジェクト: sumpfralle/beets
    def test_collect_item_and_path(self):
        path = self.create_mediafile_fixture()
        mediafile = MediaFile(path)
        item, = self.add_item_fixtures()

        item.album = mediafile.album = 'AAA'
        item.tracktotal = mediafile.tracktotal = 5
        item.title = 'TTT'
        mediafile.title = 'SSS'

        item.write()
        item.store()
        mediafile.save()

        out = self.run_with_output('--summarize', 'album:AAA', path)
        self.assertIn(u'album: AAA', out)
        self.assertIn(u'tracktotal: 5', out)
        self.assertIn(u'title: [various]', out)
        self.remove_mediafile_fixtures()
コード例 #7
0
 def _import_mtags(self, lib, opts, args):
     path, = args
     paths = [Path(path)]
     while paths:
         p = paths.pop(0)
         for child in p.iterdir():
             if child.is_dir():
                 paths.append(child)
                 continue
             loader = MTagLoader(child)
             al = []
             for path, data in loader.items():
                 matching = lib.items(PathQuery('path', path))
                 if any(m.path == path.encode() for m in matching):
                     continue
                 print('add %r' % path)
                 item = Item(path=path)
                 mf = MediaFile(syspath(item.path))
                 for field in AUDIO_FIELDS:
                     v = getattr(mf, field)
                     item[field] = v
                 values = {}
                 for tag, converter in TAGS.items():
                     v = converter.get(data)
                     if v is not None:
                         values[tag] = v
                         item[tag] = v
                 for tag, converter in DEPENDENT_TAGS.items():
                     v = converter.get(data, values)
                     if v is not None:
                         item[tag] = v
                 al.append(item)
             if al:
                 #print(al)
                 try:
                     lib.add_album(al)
                 except BaseException as e:
                     import pdb
                     pdb.post_mortem(e.__traceback__)
                     return
コード例 #8
0
    def write(self, path=None, tags=None):
        """Write the item's metadata to a media file.

        All fields in `_media_fields` are written to disk according to
        the values on this object.

        `path` is the path of the mediafile to wirte the data to. It
        defaults to the item's path.

        `tags` is a dictionary of additional metadata the should be
        written to the file.

        Can raise either a `ReadError` or a `WriteError`.
        """
        if path is None:
            path = self.path
        else:
            path = normpath(path)

        item_tags = dict(self)
        if tags is not None:
            item_tags.update(tags)
        plugins.send('write', item=self, path=path, tags=item_tags)

        try:
            mediafile = MediaFile(syspath(path),
                                  id3v23=beets.config['id3v23'].get(bool))
        except (OSError, IOError, UnreadableFileError) as exc:
            raise ReadError(self.path, exc)

        mediafile.update(item_tags)
        try:
            mediafile.save()
        except (OSError, IOError, MutagenError) as exc:
            raise WriteError(self.path, exc)

        # The file has a new mtime.
        if path == self.path:
            self.mtime = self.current_mtime()
        plugins.send('after_write', item=self, path=path)
コード例 #9
0
    def test_empty_query_n_response_no_changes(self):
        item = self.add_item_fixture(year=2016,
                                     day=13,
                                     month=3,
                                     comments=u'test comment')
        item.write()
        item_id = item.id
        self.config['zero']['fields'] = ['comments']
        self.config['zero']['update_database'] = True
        self.config['zero']['auto'] = False

        self.load_plugins('zero')
        with control_stdin('n'):
            self.run_command('zero')

        mf = MediaFile(syspath(item.path))
        item = self.lib.get_item(item_id)

        self.assertEqual(item['year'], 2016)
        self.assertEqual(mf.year, 2016)
        self.assertEqual(mf.comments, u'test comment')
        self.assertEqual(item['comments'], u'test comment')
コード例 #10
0
ファイル: helper.py プロジェクト: anaheimwhiteboy/beets
    def create_importer(self, item_count=1, album_count=1):
        """Returns import session with fixtures.

        Copies the specified number of files to a subdirectory of
        ``self.temp_dir`` and creates a ``TestImportSession`` for this
        path.
        """
        import_dir = os.path.join(self.temp_dir, 'import')
        if not os.path.isdir(import_dir):
            os.mkdir(import_dir)

        for i in range(album_count):
            album = u'album {0}'.format(i)
            album_dir = os.path.join(import_dir, album)
            os.mkdir(album_dir)
            for j in range(item_count):
                title = 'track {0}'.format(j)
                src = os.path.join(_common.RSRC, 'full.mp3')
                dest = os.path.join(album_dir, '{0}.mp3'.format(title))
                shutil.copy(src, dest)
                mediafile = MediaFile(dest)
                mediafile.update({
                    'artist': 'artist',
                    'albumartist': 'album artist',
                    'title': title,
                    'album': album,
                    'mb_albumid': None,
                    'mb_trackid': None,
                })
                mediafile.save()

        config['import']['quiet'] = True
        config['import']['autotag'] = False
        config['import']['resume'] = False

        return TestImportSession(self.lib,
                                 logfile=None,
                                 query=None,
                                 paths=[import_dir])
コード例 #11
0
    def test_no_autotag_keeps_duplicate_album(self):
        config['import']['autotag'] = False
        item = self.lib.items().get()
        self.assertEqual(item.title, u't\xeftle 0')
        self.assertTrue(os.path.isfile(item.path))

        # Imported item has the same artist and album as the one in the
        # library.
        import_file = os.path.join(self.importer.paths[0],
                                   'album 0', 'track 0.mp3')
        import_file = MediaFile(import_file)
        import_file.artist = item['artist']
        import_file.albumartist = item['artist']
        import_file.album = item['album']
        import_file.title = 'new title'

        self.importer.default_resolution = self.importer.Resolution.REMOVE
        self.importer.run()

        self.assertTrue(os.path.isfile(item.path))
        self.assertEqual(len(self.lib.albums()), 2)
        self.assertEqual(len(self.lib.items()), 2)
コード例 #12
0
    def write(self):
        """Writes the item's metadata to the associated file.
        """
        plugins.send('write', item=self)

        try:
            f = MediaFile(syspath(self.path))
        except (OSError, IOError) as exc:
            raise util.FilesystemError(exc, 'read', (self.path, ),
                                       traceback.format_exc())

        for key in ITEM_KEYS_WRITABLE:
            setattr(f, key, self[key])

        try:
            f.save(id3v23=beets.config['id3v23'].get(bool))
        except (OSError, IOError) as exc:
            raise util.FilesystemError(exc, 'write', (self.path, ),
                                       traceback.format_exc())

        # The file has a new mtime.
        self.mtime = self.current_mtime()
コード例 #13
0
    def test_add_image_structure(self):
        mediafile = self._mediafile_fixture('image')
        self.assertEqual(len(mediafile.images), 2)

        image = Image(data=self.png_data,
                      desc='the composer',
                      type=Image.TYPES.composer)
        mediafile.images += [image]
        mediafile.save()

        mediafile = MediaFile(mediafile.path)
        self.assertEqual(len(mediafile.images), 3)

        # WMA does not preserve the order, so we have to work around this
        try:
            image = filter(lambda i: i.desc == 'the composer',
                           mediafile.images)[0]
        except IndexError:
            image = None
        self.assertExtendedImageAttributes(image,
                                           desc='the composer',
                                           type=Image.TYPES.composer)
コード例 #14
0
    def write(self):
        """Write the item's metadata to the associated file.

        Can raise either a `ReadError` or a `WriteError`.
        """
        try:
            f = MediaFile(syspath(self.path))
        except (OSError, IOError) as exc:
            raise ReadError(self.path, exc)

        plugins.send('write', item=self)

        for key in ITEM_KEYS_WRITABLE:
            setattr(f, key, self[key])
        try:
            f.save(id3v23=beets.config['id3v23'].get(bool))
        except (OSError, IOError, MutagenError) as exc:
            raise WriteError(self.path, exc)

        # The file has a new mtime.
        self.mtime = self.current_mtime()
        plugins.send('after_write', item=self)
コード例 #15
0
ファイル: test_mediafile.py プロジェクト: tux-00/beets
 def _mediafile_fixture(self, name):
     name = name + '.' + self.extension
     src = os.path.join(_common.RSRC, name)
     target = os.path.join(self.temp_dir, name)
     shutil.copy(src, target)
     return MediaFile(target)
コード例 #16
0
def file_metadata(path, release):
    # type: (str,sqlite3.Row)->Tuple[Mapping[str,str],bool]
    """
    Prepare metadata dictionary for path substitution, based on file name,
    the tags stored within it and release info from the db.
    :param path: media file path
    :param release: database row with release info
    :return: pair (dict,boolean indicating if Vars.TITLE is taken from tags or
    file name). (None,None) if unable to parse the media file.
    """
    try:
        f = MediaFile(path)
    except UnreadableFileError as ex:
        logger.info("MediaFile couldn't parse: %s (%s)",
                    path.decode(headphones.SYS_ENCODING, 'replace'), str(ex))
        return None, None

    res = MetadataDict()
    # add existing tags first, these will get overwritten by musicbrainz from db
    _media_file_to_dict(f, res)
    # raw database fields come next
    _row_to_dict(release, res)

    date, year = _date_year(release)
    if not f.disc:
        disc_number = ''
    else:
        disc_number = '%d' % f.disc

    if not f.track:
        track_number = ''
    else:
        track_number = '%02d' % f.track

    if not f.title:
        basename = os.path.basename(
            path.decode(headphones.SYS_ENCODING, 'replace'))
        title = os.path.splitext(basename)[0]
        from_metadata = False
    else:
        title = f.title
        from_metadata = True

    ext = os.path.splitext(path)[1]
    if release['ArtistName'] == "Various Artists" and f.artist:
        artist_name = f.artist
    else:
        artist_name = release['ArtistName']

    if artist_name and artist_name.startswith('The '):
        sort_name = artist_name[4:] + ", The"
    else:
        sort_name = artist_name

    album_title = release['AlbumTitle']
    override_values = {
        Vars.DISC: disc_number,
        Vars.TRACK: track_number,
        Vars.TITLE: title,
        Vars.ARTIST: artist_name,
        Vars.SORT_ARTIST: sort_name,
        Vars.ALBUM: album_title,
        Vars.YEAR: year,
        Vars.DATE: date,
        Vars.EXTENSION: ext,
        Vars.TITLE_LOWER: _lower(title),
        Vars.ARTIST_LOWER: _lower(artist_name),
        Vars.SORT_ARTIST_LOWER: _lower(sort_name),
        Vars.ALBUM_LOWER: _lower(album_title),
    }
    res.add_items(override_values.iteritems())
    return res, from_metadata
コード例 #17
0
ファイル: music_encoder.py プロジェクト: samithaj/headphones
def encode(albumPath):
    use_xld = headphones.CONFIG.ENCODER == 'xld'

    # Return if xld details not found
    if use_xld:
        (xldProfile, xldFormat, xldBitrate) = getXldProfile.getXldProfile(headphones.CONFIG.XLDPROFILE)
        if not xldFormat:
            logger.error('Details for xld profile \'%s\' not found, files will not be re-encoded', xldProfile)
            return None
    else:
        xldProfile = None

    tempDirEncode = os.path.join(albumPath, "temp")
    musicFiles = []
    musicFinalFiles = []
    musicTempFiles = []
    encoder = ""

    # Create temporary directory, but remove the old one first.
    try:
        if os.path.exists(tempDirEncode):
            shutil.rmtree(tempDirEncode)
            time.sleep(1)

        os.mkdir(tempDirEncode)
    except Exception as e:
        logger.exception("Unable to create temporary directory")
        return None

    for r, d, f in os.walk(albumPath):
        for music in f:
            if any(music.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
                if not use_xld:
                    encoderFormat = headphones.CONFIG.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING)
                else:
                    xldMusicFile = os.path.join(r, music)
                    xldInfoMusic = MediaFile(xldMusicFile)
                    encoderFormat = xldFormat

                if headphones.CONFIG.ENCODERLOSSLESS:
                    ext = os.path.normpath(os.path.splitext(music)[1].lstrip(".")).lower()
                    if not use_xld and ext == 'flac' or use_xld and (ext != xldFormat and (xldInfoMusic.bitrate / 1000 > 400)):
                        musicFiles.append(os.path.join(r, music))
                        musicTemp = os.path.normpath(os.path.splitext(music)[0] + '.' + encoderFormat)
                        musicTempFiles.append(os.path.join(tempDirEncode, musicTemp))
                    else:
                        logger.debug('%s is already encoded', music)
                else:
                    musicFiles.append(os.path.join(r, music))
                    musicTemp = os.path.normpath(os.path.splitext(music)[0] + '.' + encoderFormat)
                    musicTempFiles.append(os.path.join(tempDirEncode, musicTemp))

    if headphones.CONFIG.ENCODER_PATH:
        encoder = headphones.CONFIG.ENCODER_PATH.encode(headphones.SYS_ENCODING)
    else:
        if use_xld:
            encoder = os.path.join('/Applications', 'xld')
        elif headphones.CONFIG.ENCODER == 'lame':
            if headphones.SYS_PLATFORM == "win32":
                ## NEED THE DEFAULT LAME INSTALL ON WIN!
                encoder = "C:/Program Files/lame/lame.exe"
            else:
                encoder = "lame"
        elif headphones.CONFIG.ENCODER == 'ffmpeg':
            if headphones.SYS_PLATFORM == "win32":
                encoder = "C:/Program Files/ffmpeg/bin/ffmpeg.exe"
            else:
                encoder = "ffmpeg"
        elif headphones.CONFIG.ENCODER == 'libav':
            if headphones.SYS_PLATFORM == "win32":
                encoder = "C:/Program Files/libav/bin/avconv.exe"
            else:
                encoder = "avconv"

    i = 0
    encoder_failed = False
    jobs = []

    for music in musicFiles:
        infoMusic = MediaFile(music)
        encode = False

        if use_xld:
            if xldBitrate and (infoMusic.bitrate / 1000 <= xldBitrate):
                logger.info('%s has bitrate <= %skb, will not be re-encoded', music.decode(headphones.SYS_ENCODING, 'replace'), xldBitrate)
            else:
                encode = True
        elif headphones.CONFIG.ENCODER == 'lame':
            if not any(music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.' + x) for x in ["mp3", "wav"]):
                logger.warn('Lame cannot encode %s format for %s, use ffmpeg', os.path.splitext(music)[1], music)
            else:
                if music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.mp3') and (int(infoMusic.bitrate / 1000) <= headphones.CONFIG.BITRATE):
                    logger.info('%s has bitrate <= %skb, will not be re-encoded', music, headphones.CONFIG.BITRATE)
                else:
                    encode = True
        else:
            if headphones.CONFIG.ENCODEROUTPUTFORMAT == 'ogg':
                if music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.ogg'):
                    logger.warn('Cannot re-encode .ogg %s', music.decode(headphones.SYS_ENCODING, 'replace'))
                else:
                    encode = True
            elif headphones.CONFIG.ENCODEROUTPUTFORMAT == 'mp3' or headphones.CONFIG.ENCODEROUTPUTFORMAT == 'm4a':
                if music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.' + headphones.CONFIG.ENCODEROUTPUTFORMAT) and (int(infoMusic.bitrate / 1000) <= headphones.CONFIG.BITRATE):
                    logger.info('%s has bitrate <= %skb, will not be re-encoded', music, headphones.CONFIG.BITRATE)
                else:
                    encode = True
        # encode
        if encode:
            job = (encoder, music, musicTempFiles[i], albumPath, xldProfile)
            jobs.append(job)
        else:
            musicFiles[i] = None
            musicTempFiles[i] = None

        i = i + 1

    # Encode music files
    if len(jobs) > 0:
        processes = 1

        # Use multicore if enabled
        if headphones.CONFIG.ENCODER_MULTICORE:
            if headphones.CONFIG.ENCODER_MULTICORE_COUNT == 0:
                processes = multiprocessing.cpu_count()
            else:
                processes = headphones.CONFIG.ENCODER_MULTICORE_COUNT

            logger.debug("Multi-core encoding enabled, spawning %d processes",
                processes)

        # Use multiprocessing only if it's worth the overhead. and if it is
        # enabled. If not, then use the old fashioned way.
        if processes > 1:
            with logger.listener():
                pool = multiprocessing.Pool(processes=processes)
                results = pool.map_async(command_map, jobs)

                # No new processes will be created, so close it and wait for all
                # processes to finish
                pool.close()
                pool.join()

                # Retrieve the results
                results = results.get()
        else:
            results = map(command_map, jobs)

        # The results are either True or False, so determine if one is False
        encoder_failed = not all(results)

    musicFiles = filter(None, musicFiles)
    musicTempFiles = filter(None, musicTempFiles)

    # check all files to be encoded now exist in temp directory
    if not encoder_failed and musicTempFiles:
        for dest in musicTempFiles:
            if not os.path.exists(dest):
                encoder_failed = True
                logger.error("Encoded file '%s' does not exist in the destination temp directory", dest)

    # No errors, move from temp to parent
    if not encoder_failed and musicTempFiles:
        i = 0
        for dest in musicTempFiles:
            if os.path.exists(dest):
                source = musicFiles[i]
                if headphones.CONFIG.DELETE_LOSSLESS_FILES:
                    os.remove(source)
                check_dest = os.path.join(albumPath, os.path.split(dest)[1])
                if os.path.exists(check_dest):
                    os.remove(check_dest)
                try:
                    shutil.move(dest, albumPath)
                except Exception as e:
                    logger.error('Could not move %s to %s: %s', dest, albumPath, e)
                    encoder_failed = True
                    break
            i += 1

    # remove temp directory
    shutil.rmtree(tempDirEncode)

    # Return with error if any encoding errors
    if encoder_failed:
        logger.error("One or more files failed to encode. Ensure you have the latest version of %s installed.", headphones.CONFIG.ENCODER)
        return None

    time.sleep(1)
    for r, d, f in os.walk(albumPath):
        for music in f:
            if any(music.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
                musicFinalFiles.append(os.path.join(r, music))

    if not musicTempFiles:
        logger.info('Encoding for folder \'%s\' is not required', albumPath)

    return musicFinalFiles
コード例 #18
0
def extract_metadata(f):
    """
    Scan all files in the given directory and decide on an artist, album and
    year based on the metadata. A decision is based on the number of different
    artists, albums and years found in the media files.
    """

    from headphones import logger

    # Walk directory and scan all media files
    results = []
    count = 0

    for root, dirs, files in os.walk(f):
        for file in files:
            # Count the number of potential media files
            extension = os.path.splitext(file)[1].lower()[1:]

            if extension in headphones.MEDIA_FORMATS:
                count += 1

            # Try to read the file info
            try:
                media_file = MediaFile(os.path.join(root, file))
            except (FileTypeError, UnreadableFileError):
                # Probably not a media file
                continue

            # Append metadata to file
            artist = media_file.albumartist or media_file.artist
            album = media_file.album
            year = media_file.year

            if artist and album and year:
                results.append((artist.lower(), album.lower(), year))

    # Verify results
    if len(results) == 0:
        logger.info("No metadata in media files found, ignoring.")
        return (None, None, None)

    # Require that some percentage of files have tags
    count_ratio = 0.75

    if count < (count_ratio * len(results)):
        logger.info("Counted %d media files, but only %d have tags, ignoring.", count, len(results))
        return (None, None, None)

    # Count distinct values
    artists = list(set([x[0] for x in results]))
    albums = list(set([x[1] for x in results]))
    years = list(set([x[2] for x in results]))

    # Remove things such as CD2 from album names
    if len(albums) > 1:
        new_albums = list(albums)

        # Replace occurences of e.g. CD1
        for index, album in enumerate(new_albums):
            if RE_CD_ALBUM.search(album):
                old_album = new_albums[index]
                new_albums[index] = RE_CD_ALBUM.sub("", album).strip()

                logger.debug("Stripped albumd number identifier: %s -> %s", old_album,
                             new_albums[index])

        # Remove duplicates
        new_albums = list(set(new_albums))

        # Safety check: if nothing has merged, then ignore the work. This can
        # happen if only one CD of a multi part CD is processed.
        if len(new_albums) < len(albums):
            albums = new_albums

    # All files have the same metadata, so it's trivial
    if len(artists) == 1 and len(albums) == 1:
        return (artists[0], albums[0], years[0])

    # (Lots of) different artists. Could be a featuring album, so test for this.
    if len(artists) > 1 and len(albums) == 1:
        split_artists = [RE_FEATURING.split(x) for x in artists]
        featurings = [len(split_artist) - 1 for split_artist in split_artists]
        logger.info("Album seem to feature %d different artists", sum(featurings))

        if sum(featurings) > 0:
            # Find the artist of which the least splits have been generated.
            # Ideally, this should be 0, which should be the album artist
            # itself.
            artist = split_artists[featurings.index(min(featurings))][0]

            # Done
            return (artist, albums[0], years[0])

    # Not sure what to do here.
    logger.info("Found %d artists, %d albums and %d years in metadata, so ignoring", len(artists),
                len(albums), len(years))
    logger.debug("Artists: %s, Albums: %s, Years: %s", artists, albums, years)

    return (None, None, None)
コード例 #19
0
        os.mkdir(tempDirEncode)
    except Exception, e:
        logger.exception("Unable to create temporary directory")
        return None

    for r, d, f in os.walk(albumPath):
        for music in f:
            if any(music.lower().endswith('.' + x.lower())
                   for x in headphones.MEDIA_FORMATS):
                if not XLD:
                    encoderFormat = headphones.ENCODEROUTPUTFORMAT.encode(
                        headphones.SYS_ENCODING)
                else:
                    xldMusicFile = os.path.join(r, music)
                    xldInfoMusic = MediaFile(xldMusicFile)
                    encoderFormat = xldFormat

                if (headphones.ENCODERLOSSLESS):
                    ext = os.path.normpath(
                        os.path.splitext(music)[1].lstrip(".")).lower()
                    if not XLD and ext == 'flac' or XLD and (
                            ext != xldFormat and
                        (xldInfoMusic.bitrate / 1000 > 400)):
                        musicFiles.append(os.path.join(r, music))
                        musicTemp = os.path.normpath(
                            os.path.splitext(music)[0] + '.' + encoderFormat)
                        musicTempFiles.append(
                            os.path.join(tempDirEncode, musicTemp))
                    else:
                        logger.debug('%s is already encoded', music)
コード例 #20
0
ファイル: test_ui.py プロジェクト: tux-00/beets
 def test_write_initial_key_tag(self):
     self.modify(u"initial_key=C#m")
     item = self.lib.items().get()
     mediafile = MediaFile(item.path)
     self.assertEqual(mediafile.initial_key, u'C#m')
コード例 #21
0
ファイル: test_embedart.py プロジェクト: zammottomate/beets
 def test_embed_art_from_file(self):
     album = self.add_album_fixture()
     item = album.items()[0]
     self.run_command('embedart', '-f', self.artpath)
     mediafile = MediaFile(item.path)
     self.assertEqual(mediafile.images[0].data, self.image_data)
コード例 #22
0
ファイル: test_mediafile.py プロジェクト: tux-00/beets
 def test_properties_from_readable_fields(self):
     path = os.path.join(_common.RSRC, 'full.mp3')
     mediafile = MediaFile(path)
     for field in MediaFile.readable_fields():
         self.assertTrue(hasattr(mediafile, field))
コード例 #23
0
ファイル: librarysync.py プロジェクト: virusx/headphones
def libraryScan(dir=None,
                append=False,
                ArtistID=None,
                ArtistName=None,
                cron=False):

    if cron and not headphones.LIBRARYSCAN:
        return

    if not dir:
        if not headphones.MUSIC_DIR:
            return
        else:
            dir = headphones.MUSIC_DIR

    # If we're appending a dir, it's coming from the post processor which is
    # already bytestring
    if not append:
        dir = dir.encode(headphones.SYS_ENCODING)

    if not os.path.isdir(dir):
        logger.warn('Cannot find directory: %s. Not scanning' %
                    dir.decode(headphones.SYS_ENCODING, 'replace'))
        return

    myDB = db.DBConnection()
    new_artists = []

    logger.info('Scanning music directory: %s' %
                dir.decode(headphones.SYS_ENCODING, 'replace'))

    if not append:
        # Clean up bad filepaths
        tracks = myDB.select(
            'SELECT Location from alltracks WHERE Location IS NOT NULL UNION SELECT Location from tracks WHERE Location IS NOT NULL'
        )

        for track in tracks:
            encoded_track_string = track['Location'].encode(
                headphones.SYS_ENCODING)
            if not os.path.isfile(encoded_track_string):
                myDB.action(
                    'UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE Location=?',
                    [None, None, None, track['Location']])
                myDB.action(
                    'UPDATE alltracks SET Location=?, BitRate=?, Format=? WHERE Location=?',
                    [None, None, None, track['Location']])

        del_have_tracks = myDB.select(
            'SELECT Location, Matched, ArtistName from have')

        for track in del_have_tracks:
            encoded_track_string = track['Location'].encode(
                headphones.SYS_ENCODING, 'replace')
            if not os.path.isfile(encoded_track_string):
                if track['ArtistName']:
                    #Make sure deleted files get accounted for when updating artist track counts
                    new_artists.append(track['ArtistName'])
                myDB.action('DELETE FROM have WHERE Location=?',
                            [track['Location']])
                logger.info(
                    'File %s removed from Headphones, as it is no longer on disk'
                    % encoded_track_string.decode(headphones.SYS_ENCODING,
                                                  'replace'))
        ###############myDB.action('DELETE from have')

    bitrates = []

    song_list = []
    new_song_count = 0
    file_count = 0

    latest_subdirectory = []

    for r, d, f in os.walk(dir):
        #need to abuse slicing to get a copy of the list, doing it directly will skip the element after a deleted one
        #using a list comprehension will not work correctly for nested subdirectories (os.walk keeps its original list)
        for directory in d[:]:
            if directory.startswith("."):
                d.remove(directory)

        for files in f:
            # MEDIA_FORMATS = music file extensions, e.g. mp3, flac, etc
            if any(files.lower().endswith('.' + x.lower())
                   for x in headphones.MEDIA_FORMATS):

                subdirectory = r.replace(dir, '')
                latest_subdirectory.append(subdirectory)
                if file_count == 0 and r.replace(dir, '') != '':
                    logger.info(
                        "[%s] Now scanning subdirectory %s" %
                        (dir.decode(headphones.SYS_ENCODING, 'replace'),
                         subdirectory.decode(headphones.SYS_ENCODING,
                                             'replace')))
                elif latest_subdirectory[file_count] != latest_subdirectory[
                        file_count - 1] and file_count != 0:
                    logger.info(
                        "[%s] Now scanning subdirectory %s" %
                        (dir.decode(headphones.SYS_ENCODING, 'replace'),
                         subdirectory.decode(headphones.SYS_ENCODING,
                                             'replace')))

                song = os.path.join(r, files)

                # We need the unicode path to use for logging, inserting into database
                unicode_song_path = song.decode(headphones.SYS_ENCODING,
                                                'replace')

                # Try to read the metadata
                try:
                    f = MediaFile(song)
                except (FileTypeError, UnreadableFileError):
                    logger.error(
                        "Cannot read file media file '%s'. It may be corrupted or not a media file.",
                        unicode_song_path)
                    continue

                # Grab the bitrates for the auto detect bit rate option
                if f.bitrate:
                    bitrates.append(f.bitrate)

                # Use the album artist over the artist if available
                if f.albumartist:
                    f_artist = f.albumartist
                elif f.artist:
                    f_artist = f.artist
                else:
                    f_artist = None

                # Add the song to our song list -
                # TODO: skip adding songs without the minimum requisite information (just a matter of putting together the right if statements)

                if f_artist and f.album and f.title:
                    CleanName = helpers.cleanName(f_artist + ' ' + f.album +
                                                  ' ' + f.title)
                else:
                    CleanName = None

                controlValueDict = {'Location': unicode_song_path}

                newValueDict = {
                    'TrackID': f.mb_trackid,
                    #'ReleaseID' : f.mb_albumid,
                    'ArtistName': f_artist,
                    'AlbumTitle': f.album,
                    'TrackNumber': f.track,
                    'TrackLength': f.length,
                    'Genre': f.genre,
                    'Date': f.date,
                    'TrackTitle': f.title,
                    'BitRate': f.bitrate,
                    'Format': f.format,
                    'CleanName': CleanName
                }

                #song_list.append(song_dict)
                check_exist_song = myDB.action(
                    "SELECT * FROM have WHERE Location=?",
                    [unicode_song_path]).fetchone()
                #Only attempt to match songs that are new, haven't yet been matched, or metadata has changed.
                if not check_exist_song:
                    #This is a new track
                    if f_artist:
                        new_artists.append(f_artist)
                    myDB.upsert("have", newValueDict, controlValueDict)
                    new_song_count += 1
                else:
                    if check_exist_song[
                            'ArtistName'] != f_artist or check_exist_song[
                                'AlbumTitle'] != f.album or check_exist_song[
                                    'TrackTitle'] != f.title:
                        #Important track metadata has been modified, need to run matcher again
                        if f_artist and f_artist != check_exist_song[
                                'ArtistName']:
                            new_artists.append(f_artist)
                        elif f_artist and f_artist == check_exist_song[
                                'ArtistName'] and check_exist_song[
                                    'Matched'] != "Ignored":
                            new_artists.append(f_artist)
                        else:
                            continue

                        newValueDict['Matched'] = None
                        myDB.upsert("have", newValueDict, controlValueDict)
                        myDB.action(
                            'UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE Location=?',
                            [None, None, None, unicode_song_path])
                        myDB.action(
                            'UPDATE alltracks SET Location=?, BitRate=?, Format=? WHERE Location=?',
                            [None, None, None, unicode_song_path])
                        new_song_count += 1
                    else:
                        #This track information hasn't changed
                        if f_artist and check_exist_song[
                                'Matched'] != "Ignored":
                            new_artists.append(f_artist)

                file_count += 1

    # Now we start track matching
    logger.info("%s new/modified songs found and added to the database" %
                new_song_count)
    song_list = myDB.action(
        "SELECT * FROM have WHERE Matched IS NULL AND LOCATION LIKE ?",
        [dir.decode(headphones.SYS_ENCODING, 'replace') + "%"])
    total_number_of_songs = myDB.action(
        "SELECT COUNT(*) FROM have WHERE Matched IS NULL AND LOCATION LIKE ?",
        [dir.decode(headphones.SYS_ENCODING, 'replace') + "%"]).fetchone()[0]
    logger.info("Found " + str(total_number_of_songs) +
                " new/modified tracks in: '" +
                dir.decode(headphones.SYS_ENCODING, 'replace') +
                "'. Matching tracks to the appropriate releases....")

    # Sort the song_list by most vague (e.g. no trackid or releaseid) to most specific (both trackid & releaseid)
    # When we insert into the database, the tracks with the most specific information will overwrite the more general matches

    ##############song_list = helpers.multikeysort(song_list, ['ReleaseID', 'TrackID'])
    song_list = helpers.multikeysort(song_list, ['ArtistName', 'AlbumTitle'])

    # We'll use this to give a % completion, just because the track matching might take a while
    song_count = 0
    latest_artist = []

    for song in song_list:

        latest_artist.append(song['ArtistName'])
        if song_count == 0:
            logger.info("Now matching songs by %s" % song['ArtistName'])
        elif latest_artist[song_count] != latest_artist[song_count -
                                                        1] and song_count != 0:
            logger.info("Now matching songs by %s" % song['ArtistName'])

        #print song['ArtistName']+' - '+song['AlbumTitle']+' - '+song['TrackTitle']
        song_count += 1
        completion_percentage = float(song_count) / total_number_of_songs * 100

        if completion_percentage % 10 == 0:
            logger.info("Track matching is " + str(completion_percentage) +
                        "% complete")

        #THE "MORE-SPECIFIC" CLAUSES HERE HAVE ALL BEEN REMOVED.  WHEN RUNNING A LIBRARY SCAN, THE ONLY CLAUSES THAT
        #EVER GOT HIT WERE [ARTIST/ALBUM/TRACK] OR CLEANNAME.  ARTISTID & RELEASEID ARE NEVER PASSED TO THIS FUNCTION,
        #ARE NEVER FOUND, AND THE OTHER CLAUSES WERE NEVER HIT.  FURTHERMORE, OTHER MATCHING FUNCTIONS IN THIS PROGRAM
        #(IMPORTER.PY, MB.PY) SIMPLY DO A [ARTIST/ALBUM/TRACK] OR CLEANNAME MATCH, SO IT'S ALL CONSISTENT.

        if song['ArtistName'] and song['AlbumTitle'] and song['TrackTitle']:

            track = myDB.action(
                'SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from tracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?',
                [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']
                 ]).fetchone()
            have_updated = False
            if track:
                controlValueDict = {
                    'ArtistName': track['ArtistName'],
                    'AlbumTitle': track['AlbumTitle'],
                    'TrackTitle': track['TrackTitle']
                }
                newValueDict = {
                    'Location': song['Location'],
                    'BitRate': song['BitRate'],
                    'Format': song['Format']
                }
                myDB.upsert("tracks", newValueDict, controlValueDict)

                controlValueDict2 = {'Location': song['Location']}
                newValueDict2 = {'Matched': track['AlbumID']}
                myDB.upsert("have", newValueDict2, controlValueDict2)
                have_updated = True
            else:
                track = myDB.action(
                    'SELECT CleanName, AlbumID from tracks WHERE CleanName LIKE ?',
                    [song['CleanName']]).fetchone()
                if track:
                    controlValueDict = {'CleanName': track['CleanName']}
                    newValueDict = {
                        'Location': song['Location'],
                        'BitRate': song['BitRate'],
                        'Format': song['Format']
                    }
                    myDB.upsert("tracks", newValueDict, controlValueDict)

                    controlValueDict2 = {'Location': song['Location']}
                    newValueDict2 = {'Matched': track['AlbumID']}
                    myDB.upsert("have", newValueDict2, controlValueDict2)
                    have_updated = True
                else:
                    controlValueDict2 = {'Location': song['Location']}
                    newValueDict2 = {'Matched': "Failed"}
                    myDB.upsert("have", newValueDict2, controlValueDict2)
                    have_updated = True

            alltrack = myDB.action(
                'SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from alltracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?',
                [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']
                 ]).fetchone()
            if alltrack:
                controlValueDict = {
                    'ArtistName': alltrack['ArtistName'],
                    'AlbumTitle': alltrack['AlbumTitle'],
                    'TrackTitle': alltrack['TrackTitle']
                }
                newValueDict = {
                    'Location': song['Location'],
                    'BitRate': song['BitRate'],
                    'Format': song['Format']
                }
                myDB.upsert("alltracks", newValueDict, controlValueDict)

                controlValueDict2 = {'Location': song['Location']}
                newValueDict2 = {'Matched': alltrack['AlbumID']}
                myDB.upsert("have", newValueDict2, controlValueDict2)
            else:
                alltrack = myDB.action(
                    'SELECT CleanName, AlbumID from alltracks WHERE CleanName LIKE ?',
                    [song['CleanName']]).fetchone()
                if alltrack:
                    controlValueDict = {'CleanName': alltrack['CleanName']}
                    newValueDict = {
                        'Location': song['Location'],
                        'BitRate': song['BitRate'],
                        'Format': song['Format']
                    }
                    myDB.upsert("alltracks", newValueDict, controlValueDict)

                    controlValueDict2 = {'Location': song['Location']}
                    newValueDict2 = {'Matched': alltrack['AlbumID']}
                    myDB.upsert("have", newValueDict2, controlValueDict2)
                else:
                    # alltracks may not exist if adding album manually, have should only be set to failed if not already updated in tracks
                    if not have_updated:
                        controlValueDict2 = {'Location': song['Location']}
                        newValueDict2 = {'Matched': "Failed"}
                        myDB.upsert("have", newValueDict2, controlValueDict2)

        else:
            controlValueDict2 = {'Location': song['Location']}
            newValueDict2 = {'Matched': "Failed"}
            myDB.upsert("have", newValueDict2, controlValueDict2)

        #######myDB.action('INSERT INTO have (ArtistName, AlbumTitle, TrackNumber, TrackTitle, TrackLength, BitRate, Genre, Date, TrackID, Location, CleanName, Format) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [song['ArtistName'], song['AlbumTitle'], song['TrackNumber'], song['TrackTitle'], song['TrackLength'], song['BitRate'], song['Genre'], song['Date'], song['TrackID'], song['Location'], CleanName, song['Format']])

    logger.info('Completed matching tracks from directory: %s' %
                dir.decode(headphones.SYS_ENCODING, 'replace'))

    if not append:
        logger.info('Updating scanned artist track counts')

        # Clean up the new artist list
        unique_artists = {}.fromkeys(new_artists).keys()
        current_artists = myDB.select(
            'SELECT ArtistName, ArtistID from artists')

        #There was a bug where artists with special characters (-,') would show up in new artists.
        artist_list = [
            f for f in unique_artists if helpers.cleanName(f).lower() not in
            [helpers.cleanName(x[0]).lower() for x in current_artists]
        ]
        artists_checked = [
            f for f in unique_artists if helpers.cleanName(f).lower() in
            [helpers.cleanName(x[0]).lower() for x in current_artists]
        ]

        # Update track counts

        for artist in artists_checked:
            # Have tracks are selected from tracks table and not all tracks because of duplicates
            # We update the track count upon an album switch to compliment this
            havetracks = len(
                myDB.select(
                    'SELECT TrackTitle from tracks WHERE ArtistName like ? AND Location IS NOT NULL',
                    [artist])
            ) + len(
                myDB.select(
                    'SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"',
                    [artist]))
            #Note, some people complain about having "artist have tracks" > # of tracks total in artist official releases
            # (can fix by getting rid of second len statement)
            myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistName=?',
                        [havetracks, artist])

        logger.info('Found %i new artists' % len(artist_list))

        if len(artist_list):
            if headphones.ADD_ARTISTS:
                logger.info('Importing %i new artists' % len(artist_list))
                importer.artistlist_to_mbids(artist_list)
            else:
                logger.info(
                    'To add these artists, go to Manage->Manage New Artists')
                #myDB.action('DELETE from newartists')
                for artist in artist_list:
                    myDB.action('INSERT OR IGNORE INTO newartists VALUES (?)',
                                [artist])

        if headphones.DETECT_BITRATE:
            headphones.PREFERRED_BITRATE = sum(bitrates) / len(bitrates) / 1000

    else:
        # If we're appending a new album to the database, update the artists total track counts
        logger.info('Updating artist track counts')

        havetracks = len(
            myDB.select(
                'SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL',
                [ArtistID])
        ) + len(
            myDB.select(
                'SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"',
                [ArtistName]))
        myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistID=?',
                    [havetracks, ArtistID])

    if not append:
        update_album_status()
        lastfm.getSimilar()
    logger.info('Library scan complete')
コード例 #24
0
ファイル: importer.py プロジェクト: virusx/headphones
        for track in tracks:
            try:
                f = MediaFile(track['Location'])
            except Exception, e:
                logger.info("Exception from MediaFile for: " + track['Location'] + " : " + str(e))
                continue
            controlValueDict = {"TrackID":  track['TrackID']}
            newValueDict = {"Format": f.format}
            myDB.upsert("tracks", newValueDict, controlValueDict)
        logger.info('Finished finding media format for %s files' % len(tracks))
    havetracks = myDB.select('SELECT * from have WHERE Location IS NOT NULL and Format IS NULL')
    if len(havetracks) > 0:
        logger.info('Finding media format for %s files' % len(havetracks))
        for track in havetracks:
            try:
                f = MediaFile(track['Location'])
            except Exception, e:
                logger.info("Exception from MediaFile for: " + track['Location'] + " : " + str(e))
                continue
            controlValueDict = {"TrackID":  track['TrackID']}
            newValueDict = {"Format": f.format}
            myDB.upsert("have", newValueDict, controlValueDict)
        logger.info('Finished finding media format for %s files' % len(havetracks))

def getHybridRelease(fullreleaselist):
    """
    Returns a dictionary of best group of tracks from the list of releases and
    earliest release date
    """

    if len(fullreleaselist) == 0:
コード例 #25
0
def libraryScan(dir=None,
                append=False,
                ArtistID=None,
                ArtistName=None,
                cron=False,
                artistScan=False):
    if cron and not headphones.CONFIG.LIBRARYSCAN:
        return

    if not dir:
        if not headphones.CONFIG.MUSIC_DIR:
            return
        else:
            dir = headphones.CONFIG.MUSIC_DIR

    # If we're appending a dir, it's coming from the post processor which is
    # already bytestring
    if not append or artistScan:
        dir = dir.encode(headphones.SYS_ENCODING)

    if not os.path.isdir(dir):
        logger.warn('Cannot find directory: %s. Not scanning' %
                    dir.decode(headphones.SYS_ENCODING, 'replace'))
        return

    myDB = db.DBConnection()
    new_artists = []

    logger.info('Scanning music directory: %s' %
                dir.decode(headphones.SYS_ENCODING, 'replace'))

    if not append:

        # Clean up bad filepaths. Queries can take some time, ensure all results are loaded before processing
        if ArtistID:
            tracks = myDB.action(
                'SELECT Location FROM alltracks WHERE ArtistID = ? AND Location IS NOT NULL UNION SELECT Location FROM tracks WHERE ArtistID = ? AND Location '
                'IS NOT NULL', [ArtistID, ArtistID])
        else:
            tracks = myDB.action(
                'SELECT Location FROM alltracks WHERE Location IS NOT NULL UNION SELECT Location FROM tracks WHERE Location IS NOT NULL'
            )

        locations = []
        for track in tracks:
            locations.append(track['Location'])
        for location in locations:
            encoded_track_string = location.encode(headphones.SYS_ENCODING,
                                                   'replace')
            if not os.path.isfile(encoded_track_string):
                myDB.action(
                    'UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE Location=?',
                    [None, None, None, location])
                myDB.action(
                    'UPDATE alltracks SET Location=?, BitRate=?, Format=? WHERE Location=?',
                    [None, None, None, location])

        if ArtistName:
            del_have_tracks = myDB.select(
                'SELECT Location, Matched, ArtistName FROM have WHERE ArtistName = ? COLLATE NOCASE',
                [ArtistName])
        else:
            del_have_tracks = myDB.select(
                'SELECT Location, Matched, ArtistName FROM have')

        locations = []
        for track in del_have_tracks:
            locations.append([track['Location'], track['ArtistName']])
        for location in locations:
            encoded_track_string = location[0].encode(headphones.SYS_ENCODING,
                                                      'replace')
            if not os.path.isfile(encoded_track_string):
                if location[1]:
                    # Make sure deleted files get accounted for when updating artist track counts
                    new_artists.append(location[1])
                myDB.action('DELETE FROM have WHERE Location=?', [location[0]])
                logger.info(
                    'File %s removed from Headphones, as it is no longer on disk'
                    % encoded_track_string.decode(headphones.SYS_ENCODING,
                                                  'replace'))

    bitrates = []
    song_list = []
    latest_subdirectory = []

    new_song_count = 0
    file_count = 0

    for r, d, f in helpers.walk_directory(dir):
        # Filter paths based on config. Note that these methods work directly
        # on the inputs
        helpers.path_filter_patterns(d, headphones.CONFIG.IGNORED_FOLDERS, r)
        helpers.path_filter_patterns(f, headphones.CONFIG.IGNORED_FILES, r)

        for files in f:
            # MEDIA_FORMATS = music file extensions, e.g. mp3, flac, etc
            if any(files.lower().endswith('.' + x.lower())
                   for x in headphones.MEDIA_FORMATS):
                subdirectory = r.replace(dir, '')
                latest_subdirectory.append(subdirectory)

                if file_count == 0 and r.replace(dir, '') != '':
                    logger.info(
                        "[%s] Now scanning subdirectory %s" %
                        (dir.decode(headphones.SYS_ENCODING, 'replace'),
                         subdirectory.decode(headphones.SYS_ENCODING,
                                             'replace')))
                elif latest_subdirectory[file_count] != latest_subdirectory[
                        file_count - 1] and file_count != 0:
                    logger.info(
                        "[%s] Now scanning subdirectory %s" %
                        (dir.decode(headphones.SYS_ENCODING, 'replace'),
                         subdirectory.decode(headphones.SYS_ENCODING,
                                             'replace')))

                song = os.path.join(r, files)

                # We need the unicode path to use for logging, inserting into database
                unicode_song_path = song.decode(headphones.SYS_ENCODING,
                                                'replace')

                # Try to read the metadata
                try:
                    f = MediaFile(song)
                except (FileTypeError, UnreadableFileError):
                    logger.warning(
                        "Cannot read media file '%s', skipping. It may be corrupted or not a media file.",
                        unicode_song_path)
                    continue
                except IOError:
                    logger.warning(
                        "Cannnot read media file '%s', skipping. Does the file exists?",
                        unicode_song_path)
                    continue

                # Grab the bitrates for the auto detect bit rate option
                if f.bitrate:
                    bitrates.append(f.bitrate)

                # Use the album artist over the artist if available
                if f.albumartist:
                    f_artist = f.albumartist
                elif f.artist:
                    f_artist = f.artist
                else:
                    f_artist = None

                # Add the song to our song list -
                # TODO: skip adding songs without the minimum requisite information (just a matter of putting together the right if statements)

                if f_artist and f.album and f.title:
                    CleanName = helpers.clean_name(f_artist + ' ' + f.album +
                                                   ' ' + f.title)
                else:
                    CleanName = None

                controlValueDict = {'Location': unicode_song_path}

                newValueDict = {
                    'TrackID': f.mb_trackid,
                    # 'ReleaseID' : f.mb_albumid,
                    'ArtistName': f_artist,
                    'AlbumTitle': f.album,
                    'TrackNumber': f.track,
                    'TrackLength': f.length,
                    'Genre': f.genre,
                    'Date': f.date,
                    'TrackTitle': f.title,
                    'BitRate': f.bitrate,
                    'Format': f.format,
                    'CleanName': CleanName
                }

                # song_list.append(song_dict)
                check_exist_song = myDB.action(
                    "SELECT * FROM have WHERE Location=?",
                    [unicode_song_path]).fetchone()
                # Only attempt to match songs that are new, haven't yet been matched, or metadata has changed.
                if not check_exist_song:
                    # This is a new track
                    if f_artist:
                        new_artists.append(f_artist)
                    myDB.upsert("have", newValueDict, controlValueDict)
                    new_song_count += 1
                else:
                    if check_exist_song[
                            'ArtistName'] != f_artist or check_exist_song[
                                'AlbumTitle'] != f.album or check_exist_song[
                                    'TrackTitle'] != f.title:
                        # Important track metadata has been modified, need to run matcher again
                        if f_artist and f_artist != check_exist_song[
                                'ArtistName']:
                            new_artists.append(f_artist)
                        elif f_artist and f_artist == check_exist_song['ArtistName'] and \
                                        check_exist_song['Matched'] != "Ignored":
                            new_artists.append(f_artist)
                        else:
                            continue

                        newValueDict['Matched'] = None
                        myDB.upsert("have", newValueDict, controlValueDict)
                        myDB.action(
                            'UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE Location=?',
                            [None, None, None, unicode_song_path])
                        myDB.action(
                            'UPDATE alltracks SET Location=?, BitRate=?, Format=? WHERE Location=?',
                            [None, None, None, unicode_song_path])
                        new_song_count += 1
                    else:
                        # This track information hasn't changed
                        if f_artist and check_exist_song[
                                'Matched'] != "Ignored":
                            new_artists.append(f_artist)

                file_count += 1

    # Now we start track matching
    logger.info("%s new/modified songs found and added to the database" %
                new_song_count)
    song_list = myDB.action(
        "SELECT * FROM have WHERE Matched IS NULL AND LOCATION LIKE ?",
        [dir.decode(headphones.SYS_ENCODING, 'replace') + "%"])
    total_number_of_songs = \
        myDB.action("SELECT COUNT(*) FROM have WHERE Matched IS NULL AND LOCATION LIKE ?",
                    [dir.decode(headphones.SYS_ENCODING, 'replace') + "%"]).fetchone()[0]
    logger.info("Found " + str(total_number_of_songs) +
                " new/modified tracks in: '" +
                dir.decode(headphones.SYS_ENCODING, 'replace') +
                "'. Matching tracks to the appropriate releases....")

    # Sort the song_list by most vague (e.g. no trackid or releaseid) to most specific (both trackid & releaseid)
    # When we insert into the database, the tracks with the most specific information will overwrite the more general matches

    # song_list = helpers.multikeysort(song_list, ['ReleaseID', 'TrackID'])
    song_list = helpers.multikeysort(song_list, ['ArtistName', 'AlbumTitle'])

    # We'll use this to give a % completion, just because the track matching might take a while
    song_count = 0
    latest_artist = []
    last_completion_percentage = 0
    prev_artist_name = None
    artistid = None

    for song in song_list:

        latest_artist.append(song['ArtistName'])
        if song_count == 0:
            logger.info("Now matching songs by %s" % song['ArtistName'])
        elif latest_artist[song_count] != latest_artist[song_count -
                                                        1] and song_count != 0:
            logger.info("Now matching songs by %s" % song['ArtistName'])

        song_count += 1
        completion_percentage = math.floor(
            float(song_count) / total_number_of_songs * 1000) / 10

        if completion_percentage >= (last_completion_percentage + 10):
            logger.info("Track matching is " + str(completion_percentage) +
                        "% complete")
            last_completion_percentage = completion_percentage

        # THE "MORE-SPECIFIC" CLAUSES HERE HAVE ALL BEEN REMOVED.  WHEN RUNNING A LIBRARY SCAN, THE ONLY CLAUSES THAT
        # EVER GOT HIT WERE [ARTIST/ALBUM/TRACK] OR CLEANNAME.  ARTISTID & RELEASEID ARE NEVER PASSED TO THIS FUNCTION,
        # ARE NEVER FOUND, AND THE OTHER CLAUSES WERE NEVER HIT.  FURTHERMORE, OTHER MATCHING FUNCTIONS IN THIS PROGRAM
        # (IMPORTER.PY, MB.PY) SIMPLY DO A [ARTIST/ALBUM/TRACK] OR CLEANNAME MATCH, SO IT'S ALL CONSISTENT.

        albumid = None

        if song['ArtistName'] and song['CleanName']:
            artist_name = song['ArtistName']
            clean_name = song['CleanName']

            # Only update if artist is in the db
            if artist_name != prev_artist_name:
                prev_artist_name = artist_name
                artistid = None

                artist_lookup = "\"" + artist_name.replace("\"", "\"\"") + "\""

                try:
                    dbartist = myDB.select(
                        'SELECT DISTINCT ArtistID, ArtistName FROM artists WHERE ArtistName LIKE '
                        + artist_lookup + '')
                except:
                    dbartist = None
                if not dbartist:
                    dbartist = myDB.select(
                        'SELECT DISTINCT ArtistID, ArtistName FROM tracks WHERE CleanName = ?',
                        [clean_name])
                    if not dbartist:
                        dbartist = myDB.select(
                            'SELECT DISTINCT ArtistID, ArtistName FROM alltracks WHERE CleanName = ?',
                            [clean_name])
                        if not dbartist:
                            clean_artist = helpers.clean_name(artist_name)
                            if clean_artist:
                                dbartist = myDB.select(
                                    'SELECT DISTINCT ArtistID, ArtistName FROM tracks WHERE CleanName >= ? and CleanName < ?',
                                    [clean_artist, clean_artist + '{'])
                                if not dbartist:
                                    dbartist = myDB.select(
                                        'SELECT DISTINCT ArtistID, ArtistName FROM alltracks WHERE CleanName >= ? and CleanName < ?',
                                        [clean_artist, clean_artist + '{'])

                if dbartist:
                    artistid = dbartist[0][0]

            if artistid:

                # This was previously using Artist, Album, Title with a SELECT LIKE ? and was not using an index
                # (Possible issue: https://stackoverflow.com/questions/37845854/python-sqlite3-not-using-index-with-like)
                # Now selects/updates using CleanName index (may have to revert if not working)

                # matching on CleanName should be enough, ensure it's the same artist just in case

                # Update tracks
                track = myDB.action(
                    'SELECT AlbumID, ArtistName FROM tracks WHERE CleanName = ? AND ArtistID = ?',
                    [clean_name, artistid]).fetchone()
                if track:
                    albumid = track['AlbumID']
                    myDB.action(
                        'UPDATE tracks SET Location = ?, BitRate = ?, Format = ? WHERE CleanName = ? AND ArtistID = ?',
                        [
                            song['Location'], song['BitRate'], song['Format'],
                            clean_name, artistid
                        ])

                # Update alltracks
                alltrack = myDB.action(
                    'SELECT AlbumID, ArtistName FROM alltracks WHERE CleanName = ? AND ArtistID = ?',
                    [clean_name, artistid]).fetchone()
                if alltrack:
                    albumid = alltrack['AlbumID']
                    myDB.action(
                        'UPDATE alltracks SET Location = ?, BitRate = ?, Format = ? WHERE CleanName = ? AND ArtistID = ?',
                        [
                            song['Location'], song['BitRate'], song['Format'],
                            clean_name, artistid
                        ])

        # Update have
        controlValueDict2 = {'Location': song['Location']}
        if albumid:
            newValueDict2 = {'Matched': albumid}
        else:
            newValueDict2 = {'Matched': "Failed"}
        myDB.upsert("have", newValueDict2, controlValueDict2)

        # myDB.action('INSERT INTO have (ArtistName, AlbumTitle, TrackNumber, TrackTitle, TrackLength, BitRate, Genre, Date, TrackID, Location, CleanName, Format) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [song['ArtistName'], song['AlbumTitle'], song['TrackNumber'], song['TrackTitle'], song['TrackLength'], song['BitRate'], song['Genre'], song['Date'], song['TrackID'], song['Location'], CleanName, song['Format']])

    logger.info('Completed matching tracks from directory: %s' %
                dir.decode(headphones.SYS_ENCODING, 'replace'))

    if not append or artistScan:
        logger.info('Updating scanned artist track counts')

        # Clean up the new artist list
        unique_artists = {}.fromkeys(new_artists).keys()

        # # Don't think we need to do this, check the db instead below
        #
        # # artist scan
        # if ArtistName:
        #     current_artists = [[ArtistName]]
        # # directory scan
        # else:
        #     current_artists = myDB.select('SELECT ArtistName, ArtistID FROM artists WHERE ArtistName IS NOT NULL')
        #
        # # There was a bug where artists with special characters (-,') would show up in new artists.
        #
        # # artist_list = scanned artists not in the db
        # artist_list = [
        #     x for x in unique_artists
        #     if helpers.clean_name(x).lower() not in [
        #         helpers.clean_name(y[0]).lower()
        #         for y in current_artists
        #         ]
        #     ]
        #
        # # artists_checked = scanned artists that exist in the db
        # artists_checked = [
        #     x for x in unique_artists
        #     if helpers.clean_name(x).lower() in [
        #         helpers.clean_name(y[0]).lower()
        #         for y in current_artists
        #         ]
        #     ]

        new_artist_list = []

        for artist in unique_artists:

            if not artist:
                continue

            logger.info('Processing artist: %s' % artist)

            # check if artist is already in the db
            artist_lookup = "\"" + artist.replace("\"", "\"\"") + "\""

            try:
                dbartist = myDB.select(
                    'SELECT DISTINCT ArtistID, ArtistName FROM artists WHERE ArtistName LIKE '
                    + artist_lookup + '')
            except:
                dbartist = None
            if not dbartist:
                clean_artist = helpers.clean_name(artist)
                if clean_artist:
                    dbartist = myDB.select(
                        'SELECT DISTINCT ArtistID, ArtistName FROM tracks WHERE CleanName >= ? and CleanName < ?',
                        [clean_artist, clean_artist + '{'])
                    if not dbartist:
                        dbartist = myDB.select(
                            'SELECT DISTINCT ArtistID, ArtistName FROM alltracks WHERE CleanName >= ? and CleanName < ?',
                            [clean_artist, clean_artist + '{'])

            # new artist not in db, add to list
            if not dbartist:
                new_artist_list.append(artist)
            else:

                # artist in db, update have track counts
                artistid = dbartist[0][0]

                # Have tracks are selected from tracks table and not all tracks because of duplicates
                # We update the track count upon an album switch to compliment this

                # havetracks = (
                #     len(myDB.select(
                #         'SELECT TrackTitle from tracks WHERE ArtistName like ? AND Location IS NOT NULL',
                #         [artist])) + len(myDB.select(
                #             'SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"',
                #             [artist]))
                # )

                try:
                    havetracks = (len(
                        myDB.select(
                            'SELECT ArtistID From tracks WHERE ArtistID = ? AND Location IS NOT NULL',
                            [artistid])
                    ) + len(
                        myDB.select(
                            'SELECT ArtistName FROM have WHERE ArtistName LIKE '
                            + artist_lookup + ' AND Matched = "Failed"')))
                except Exception as e:
                    logger.warn('Error updating counts for artist: %s: %s' %
                                (artist, e))

                # Note: some people complain about having "artist have tracks" > # of tracks total in artist official releases
                # (can fix by getting rid of second len statement)

                if havetracks:
                    myDB.action(
                        'UPDATE artists SET HaveTracks = ? WHERE ArtistID = ?',
                        [havetracks, artistid])

                    # Update albums to downloaded
                    update_album_status(ArtistID=artistid)

        logger.info('Found %i new artists' % len(new_artist_list))

        # Add scanned artists not in the db
        if new_artist_list:
            if headphones.CONFIG.AUTO_ADD_ARTISTS:
                logger.info('Importing %i new artists' % len(new_artist_list))
                importer.artistlist_to_mbids(new_artist_list)
            else:
                logger.info(
                    'To add these artists, go to Manage->Manage New Artists')
                # myDB.action('DELETE from newartists')
                for artist in new_artist_list:
                    myDB.action('INSERT OR IGNORE INTO newartists VALUES (?)',
                                [artist])

        if headphones.CONFIG.DETECT_BITRATE and bitrates:
            headphones.CONFIG.PREFERRED_BITRATE = sum(bitrates) / len(
                bitrates) / 1000

    else:
        # If we're appending a new album to the database, update the artists total track counts
        logger.info('Updating artist track counts')

        artist_lookup = "\"" + ArtistName.replace("\"", "\"\"") + "\""
        try:
            havetracks = len(
                myDB.select(
                    'SELECT ArtistID FROM tracks WHERE ArtistID = ? AND Location IS NOT NULL',
                    [ArtistID])
            ) + len(
                myDB.select(
                    'SELECT ArtistName FROM have WHERE ArtistName LIKE ' +
                    artist_lookup + ' AND Matched = "Failed"'))
        except Exception as e:
            logger.warn('Error updating counts for artist: %s: %s' %
                        (ArtistName, e))

        if havetracks:
            myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistID=?',
                        [havetracks, ArtistID])

    # Moved above to call for each artist
    # if not append:
    #     update_album_status()

    if not append and not artistScan:
        lastfm.getSimilar()

    if ArtistName:
        logger.info('Scanning complete for artist: %s', ArtistName)
    else:
        logger.info('Library scan complete')
コード例 #26
0
ファイル: cli_test.py プロジェクト: pkel/beets-alternatives
    def test_external(self):
        external_dir = os.path.join(self.mkdtemp(), 'myplayer')
        self.config['convert']['formats'] = {
            'aac': {
                'command': 'bash -c "cp \'$source\' \'$dest\';' +
                           'printf ISAAC >> \'$dest\'"',
                'extension': 'm4a'
            },
        }
        self.config['alternatives'] = {
            'myplayer': {
                'directory': external_dir,
                'paths': {'default': u'$artist/$title'},
                'formats': u'aac mp3',
                'query': u'onplayer:true',
                'removable': True,
            }
        }

        self.add_album(artist='Bach', title='was mp3', format='mp3')
        self.add_album(artist='Bach', title='was m4a', format='m4a')
        self.add_album(artist='Bach', title='was ogg', format='ogg')
        self.add_album(artist='Beethoven', title='was ogg', format='ogg')

        external_from_mp3 = bytestring_path(
            os.path.join(external_dir, 'Bach', 'was mp3.mp3'))
        external_from_m4a = bytestring_path(
            os.path.join(external_dir, 'Bach', 'was m4a.m4a'))
        external_from_ogg = bytestring_path(
            os.path.join(external_dir, 'Bach', 'was ogg.m4a'))
        external_beet = bytestring_path(
            os.path.join(external_dir, 'Beethoven', 'was ogg.m4a'))

        self.runcli('modify', '--yes', 'onplayer=true', 'artist:Bach')
        with control_stdin('y'):
            out = self.runcli('alt', 'update', 'myplayer')
            self.assertIn('Do you want to create the collection?', out)

        self.assertNotFileTag(external_from_mp3, b'ISAAC')
        self.assertNotFileTag(external_from_m4a, b'ISAAC')
        self.assertFileTag(external_from_ogg, b'ISAAC')
        self.assertFalse(os.path.isfile(external_beet))

        self.runcli('modify', '--yes', 'composer=JSB', 'artist:Bach')

        list_output = self.runcli(
            'alt', 'list-tracks', 'myplayer', '--format', '$artist $title')
        self.assertEqual(list_output, '\n'.join(
            ['Bach was mp3', 'Bach was m4a', 'Bach was ogg', '']))

        self.runcli('alt', 'update', 'myplayer')
        mediafile = MediaFile(syspath(external_from_ogg))
        self.assertEqual(mediafile.composer, 'JSB')

        self.runcli('modify', '--yes', 'onplayer!', 'artist:Bach')
        self.runcli('modify', '--album', '--yes',
                    'onplayer=true', 'albumartist:Beethoven')
        self.runcli('alt', 'update', 'myplayer')

        list_output = self.runcli(
            'alt', 'list-tracks', 'myplayer', '--format', '$artist')
        self.assertEqual(list_output, 'Beethoven\n')

        self.assertFalse(os.path.isfile(external_from_mp3))
        self.assertFalse(os.path.isfile(external_from_m4a))
        self.assertFalse(os.path.isfile(external_from_ogg))
        self.assertFileTag(external_beet, b'ISAAC')
コード例 #27
0
ファイル: test_library.py プロジェクト: ykumar6/beets
 def test_write_custom_tags(self):
     item = self.add_item_fixture(artist='old artist')
     item.write(tags={'artist': 'new artist'})
     self.assertNotEqual(item.artist, 'new artist')
     self.assertEqual(MediaFile(item.path).artist, 'new artist')
コード例 #28
0
 def modifyFile(self, path, title='a different title'):
     mediafile = MediaFile(path)
     mediafile.title = title
     mediafile.save()