Exemple #1
0
    def _scrub_item(self, item, restore=True):
        """Remove tags from an Item's associated file and, if `restore`
        is enabled, write the database's tags back to the file.
        """
        # Get album art if we need to restore it.
        if restore:
            try:
                mf = mediafile.MediaFile(util.syspath(item.path),
                                         config['id3v23'].get(bool))
            except mediafile.UnreadableFileError as exc:
                self._log.error('could not open file to scrub: {0}', exc)
                return
            images = mf.images

        # Remove all tags.
        self._scrub(item.path)

        # Restore tags, if enabled.
        if restore:
            self._log.debug('writing new tags after scrub')
            item.try_write()
            if images:
                self._log.debug('restoring art')
                try:
                    mf = mediafile.MediaFile(util.syspath(item.path),
                                             config['id3v23'].get(bool))
                    mf.images = images
                    mf.save()
                except mediafile.UnreadableFileError as exc:
                    self._log.error('could not write tags: {0}', exc)
    def setUp(self):
        self.create_temp_dir()
        src = os.path.join(_common.RSRC, b'full.mp3')
        self.path = os.path.join(self.temp_dir, b'test.mp3')
        shutil.copy(src, self.path)

        self.mf = mediafile.MediaFile(self.path)
Exemple #3
0
    def read_items(self, lib, items, album=None, pretend=False):
        changed = set()
        albums = {}
        dels = []

        for item in items:
            mf = mediafile.MediaFile(item.path)
            advisory = mf.itunesadvisory
            if advisory:
                if advisory == 1:
                    if album:
                        albums[album.id] = album
                    elif not item.singleton and item.album_id not in albums:
                        albums[item.album_id] = item.get_album()

                if print_and_modify(item, {'advisory': advisory}, dels):
                    changed.add(item)

        for album_id, album in albums.items():
            if print_and_modify(album, {'albumadvisory': 1}, dels):
                changed.add(album)

        if not pretend:
            with lib.transaction():
                for obj in changed:
                    obj.store()
 def test_release_time_with_t(self):
     # Ensures that release times delimited by Ts are ignored.
     # The iTunes Store produces such files.
     t_time = mediafile.MediaFile(os.path.join(_common.RSRC, b't_time.m4a'))
     self.assertEqual(t_time.year, 1987)
     self.assertEqual(t_time.month, 3)
     self.assertEqual(t_time.day, 31)
 def test_discc_alternate_field(self):
     # Different taggers use different vorbis comments to reflect
     # the disc and disc count fields: ensure that the alternative
     # style works.
     f = mediafile.MediaFile(os.path.join(_common.RSRC, b'discc.ogg'))
     self.assertEqual(f.disc, 4)
     self.assertEqual(f.disctotal, 5)
Exemple #6
0
 def _make_test(self, ext=b'mp3', id3v23=False):
     self.create_temp_dir()
     src = os.path.join(_common.RSRC,
                        b'full.' + ext)
     self.path = os.path.join(self.temp_dir,
                              b'test.' + ext)
     shutil.copy(src, self.path)
     return mediafile.MediaFile(self.path, id3v23=id3v23)
 def test_release_time_with_space(self):
     # Ensures that release times delimited by spaces are ignored.
     # Amie Street produces such files.
     space_time = mediafile.MediaFile(
         os.path.join(_common.RSRC, b'space_time.mp3'))
     self.assertEqual(space_time.year, 2009)
     self.assertEqual(space_time.month, 9)
     self.assertEqual(space_time.day, 4)
Exemple #8
0
 def test_write(self):
     src = os.path.join(_common.RSRC, b'empty.flac')
     path = os.path.join(self.temp_dir, b'test.flac')
     shutil.copy(src, path)
     mf = mediafile.MediaFile(path)
     mf.read_only_field = "something terrible"
     mf.save()
     self.assertNotIn(self.key, mf.mgfile.tags)
 def test_emptylist(self):
     # Some files have an ID3 frame that has a list with no elements.
     # This is very hard to produce, so this is just the first 8192
     # bytes of a file found "in the wild".
     emptylist = mediafile.MediaFile(
         os.path.join(_common.RSRC, b'emptylist.mp3'))
     genre = emptylist.genre
     self.assertEqual(genre, None)
Exemple #10
0
def get_art(log, item):
    # Extract the art.
    try:
        mf = mediafile.MediaFile(syspath(item.path))
    except mediafile.UnreadableFileError as exc:
        log.warning('Could not extract art from {0}: {1}',
                    displayable_path(item.path), exc)
        return

    return mf.art
Exemple #11
0
def cover_art():
    cache = current_app.cache

    eid = request.values["id"]
    try:
        fid = get_entity_id(Folder, eid)
    except GenericError:
        fid = None
    try:
        tid = get_entity_id(Track, eid)
    except GenericError:
        tid = None

    if not fid and not tid:
        raise GenericError("Invalid ID")

    if fid and Folder.exists(id=eid):
        res = get_entity(Folder)
        if not res.cover_art or not os.path.isfile(
                os.path.join(res.path, res.cover_art)):
            raise NotFound("Cover art")
        cover_path = os.path.join(res.path, res.cover_art)
    elif tid and Track.exists(id=eid):
        cache_key = "{}-cover".format(eid)
        try:
            cover_path = cache.get(cache_key)
        except CacheMiss:
            res = get_entity(Track)
            try:
                art = mediafile.MediaFile(res.path).art
            except mediafile.UnreadableFileError:
                raise NotFound("Cover art")
            cover_path = cache.set(cache_key, art)
    else:
        raise NotFound("Entity")

    size = request.values.get("size")
    if size:
        size = int(size)
    else:
        return send_file(cover_path)

    with Image.open(cover_path) as im:
        mimetype = "image/{}".format(im.format.lower())
        if size > im.width and size > im.height:
            return send_file(cover_path, mimetype=mimetype)

        cache_key = "{}-cover-{}".format(eid, size)
        try:
            return send_file(cache.get(cache_key), mimetype=mimetype)
        except CacheMiss:
            im.thumbnail([size, size], Image.ANTIALIAS)
            with cache.set_fileobj(cache_key) as fp:
                im.save(fp, im.format)
            return send_file(cache.get(cache_key), mimetype=mimetype)
Exemple #12
0
 def write_advisory(self, lib, opts, args):
     self.config.set_args(opts)
     for item in lib.items(u'advisory:1..'):
         mf = mediafile.MediaFile(item.path)
         if item.advisory != mf.itunesadvisory:
             fakeitem = new_item(item)
             fakeitem.advisory = mf.itunesadvisory
             show_model_changes(item, fakeitem)
             if not opts.pretend:
                 mf.itunesadvisory = item.advisory
                 mf.save()
Exemple #13
0
    def emitter():
        fields = list(mediafile.MediaFile.readable_fields())
        fields.remove('images')
        mf = mediafile.MediaFile(syspath(path))
        tags = {}
        for field in fields:
            tags[field] = getattr(mf, field)
        tags['art'] = mf.art is not None
        # create a temporary Item to take advantage of __format__
        item = Item.from_path(syspath(path))

        return tags, item
Exemple #14
0
def import_track_to_db(path: Path):
    """Import track to database using mediafile"""
    try:
        mf = mediafile.MediaFile(path)
    except (mediafile.FileTypeError, mediafile.UnreadableFileError):
        logger.debug(f"Error reading tags from file '{path}'")
        return

    mtime = os.path.getmtime(path)
    mtime_timestamp = datetime.datetime.fromtimestamp(mtime)

    track, created = Track.objects.update_or_create(
        file=str(path),
        artist=mf.artist,
        title=mf.title,
        comment=mf.comments,
        bpm=mf.bpm,
        key=mf.initial_key,
        duration=mf.length,
        bitrate=mf.bitrate,
        album=mf.album,
        import_date=get_geob_date(path),
    )
    track_was_updated = track.file_mtime and track.file_mtime < mtime_timestamp
    track.file_mtime = mtime_timestamp

    for image in mf.images:
        if not image.mime_type:
            logger.error(f"Invalid mimetype: {image}")
            continue
        image_extension = image.mime_type.split("/")[-1]
        track_image = TrackImage(track=track, desc=image.desc)
        image_file = UmaskNamedTemporaryFile(mode="wb",
                                             suffix=f".{image_extension}")
        image_file.write(image.data)
        track_image.image.save(os.path.basename(image_file.name),
                               ContentFile(image.data))
        track_image.save()

    if created or track_was_updated:
        if mf.popm:
            for email in mf.popm.keys():
                rating, _ = TrackRating.objects.update_or_create(track=track,
                                                                 email=email)
                rating.rating = mf.popm[email]["rating"]
                rating.count = mf.popm[email]["count"]
                rating.save()

            track.file_mtime = mtime_timestamp
        track.save(update_fields=["file_mtime"])
    def test_extended_field_write(self):
        plugin = BeetsPlugin()
        plugin.add_media_field('customtag', field_extension)

        try:
            mf = self._mediafile_fixture('empty')
            mf.customtag = u'F#'
            mf.save()

            mf = mediafile.MediaFile(mf.path)
            self.assertEqual(mf.customtag, u'F#')

        finally:
            delattr(mediafile.MediaFile, 'customtag')
            Item._media_fields.remove('customtag')
Exemple #16
0
def _cover_from_track(tid):
    """Extract and return a path to a track's cover art

    Returns None if no cover art is available.
    """
    cache = current_app.cache
    cache_key = "{}-cover".format(tid)
    try:
        return cache.get(cache_key)
    except CacheMiss:
        obj = Track[tid]
        try:
            return cache.set(cache_key, mediafile.MediaFile(obj.path).art)
        except mediafile.UnreadableFileError:
            return None
    def test_write_extended_tag_from_item(self):
        plugin = BeetsPlugin()
        plugin.add_media_field('customtag', field_extension)

        try:
            mf = self._mediafile_fixture('empty')
            self.assertIsNone(mf.customtag)

            item = Item(path=mf.path, customtag=u'Gb')
            item.write()
            mf = mediafile.MediaFile(mf.path)
            self.assertEqual(mf.customtag, u'Gb')

        finally:
            delattr(mediafile.MediaFile, 'customtag')
            Item._media_fields.remove('customtag')
Exemple #18
0
def save_tag_to_audio_file(audio_file, tracklisting):
    """
    Saves tag to audio file.
    """
    print("Trying to tag {}".format(audio_file))
    f = mediafile.MediaFile(audio_file)

    if not f.lyrics:
        print("No tracklisting present. Creating lyrics tag.")
        f.lyrics = 'Tracklisting' + '\n' + tracklisting
    elif tracklisting not in f.lyrics:
        print("Appending tracklisting to existing lyrics tag.")
        f.lyrics = f.lyrics + '\n\n' + 'Tracklisting' + '\n' + tracklisting
    else:
        print("Tracklisting already present. Not modifying file.")
        raise TagNotNeededError

    f.save()
    print("Saved tag to file:", audio_file)
Exemple #19
0
def _write_tags(track: Track):
    """Write tags to a track's file."""
    log.info(f"Writing tags for '{track}'.")

    audio_file = mediafile.MediaFile(track.path)

    audio_file.album = track.album
    audio_file.albumartist = track.albumartist
    audio_file.artist = track.artist
    audio_file.date = track.date
    audio_file.disc = track.disc
    audio_file.disctotal = track.disc_total
    audio_file.genres = track.genres
    audio_file.mb_releasetrackid = track.mb_track_id
    audio_file.mb_albumid = track.mb_album_id
    audio_file.title = track.title
    audio_file.track = track.track_num

    audio_file.save()
Exemple #20
0
    def emitter(included_keys):
        if included_keys == '*':
            fields = tag_fields()
        else:
            fields = included_keys
        if 'images' in fields:
            # We can't serialize the image data.
            fields.remove('images')
        mf = mediafile.MediaFile(syspath(path))
        tags = {}
        for field in fields:
            if field == 'art':
                tags[field] = mf.art is not None
            else:
                tags[field] = getattr(mf, field, None)

        # create a temporary Item to take advantage of __format__
        item = Item.from_path(syspath(path))

        return tags, item
Exemple #21
0
    def from_path(cls, path: Path) -> "TrackSchema":
        logger = logging.getLogger(__name__)
        logger.debug(f"Importing track: {path.name}")

        file_modification_date = datetime.datetime.fromtimestamp(
            path.stat().st_mtime)

        track = TrackSchema(file=path, file_mtime=file_modification_date)

        try:
            media_file = mediafile.MediaFile(path)
            track.artist = media_file.artist
            track.title = media_file.title
            track.comment = media_file.comments
            track.bpm = media_file.bpm
            track.key = media_file.initial_key
            track.duration = media_file.length
            track.bitrate = media_file.bitrate
            track.album = media_file.album
        except (mediafile.FileTypeError,
                mediafile.UnreadableFileError) as error:
            track.import_error = str(error)

        return track
Exemple #22
0
 def __try_load_tag(self, path):
     try:
         return mediafile.MediaFile(path)
     except mediafile.UnreadableFileError:
         return None
Exemple #23
0
    def run(self):
        # gather metadata from the flac file
        flac_info = mediafile.MediaFile(self.flac_file)

        # determine the new filename
        transcode_file = re.sub(re.escape(self.flac_dir), self.transcode_dir,
                                self.flac_file)
        transcode_file = re.sub('\.flac$', '', transcode_file)

        # make sure the path exists
        if not os.path.exists(os.path.dirname(transcode_file)):
            os.makedirs(os.path.dirname(transcode_file))

        # determine the correct transcoding process
        flac_decoder = 'flac -dcs -- "%(FLAC)s"'

        lame_encoder = 'lame -S %(OPTS)s - "%(FILE)s" > /dev/null 2> /dev/null'
        ogg_encoder = 'oggenc -Q %(OPTS)s -o "%(FILE)s" - > /dev/null 2> /dev/null'
        ffmpeg_encoder = 'ffmpeg %(OPTS)s "%(FILE)s" > /dev/null 2> /dev/null'
        nero_encoder = 'neroAacEnc %(OPTS)s -if - -of "%(FILE)s" > /dev/null 2> /dev/null'
        flac_encoder = 'flac %(OPTS)s -o "%(FILE)s" - > /dev/null 2> /dev/null'

        dither_command = 'sox -t wav - -b 16 -r 44100 -t wav -'

        transcoding_steps = [flac_decoder]

        if self.dither:
            transcoding_steps.append(dither_command)

        if encoders[self.codec]['enc'] == 'lame':
            transcoding_steps.append(lame_encoder)
            transcode_file += ".mp3"
        elif encoders[self.codec]['enc'] == 'oggenc':
            transcoding_steps.append(ogg_encoder)
            transcode_file += ".ogg"
        elif encoders[self.codec]['enc'] == 'ffmpeg':
            transcoding_steps.append(ffmpeg_encoder)
            transcode_file += ".alac"
        elif encoders[self.codec]['enc'] == 'neroAacEnc':
            transcoding_steps.append(nero_encoder)
            transcode_file += ".m4a"
        elif encoders[self.codec]['enc'] == 'flac':
            transcoding_steps.append(flac_encoder)
            transcode_file += ".flac"

        transcode_args = {
            'FLAC': self.flac_file,
            'FILE': transcode_file,
            'OPTS': encoders[self.codec]['opts']
        }

        transcode_command = ' | '.join(transcoding_steps) % transcode_args

        if self.dither and self.codec == 'FLAC':
            # for some reason, FLAC | SoX | FLAC does not work.
            # use files instead.
            transcode_args['TEMP'] = self.flac_file + ".wav"
            transcode_command = ''.join([flac_decoder, ' | ', dither_command, ' > "%(TEMP)s"; ', \
                    flac_encoder, ' < "%(TEMP)s"; rm "%(TEMP)s"']) % transcode_args

        # transcode the file
        try:
            os.system(transcode_command)
        except UnicodeEncodeError:
            os.system(transcode_command.encode('utf-8'))

        # tag the file
        transcode_info = mediafile.MediaFile(transcode_file)
        skip = ['format', 'type', 'bitrate', 'mgfile', 'save']
        for attribute in dir(flac_info):
            if not attribute.startswith('_') and attribute not in skip:
                try:
                    setattr(transcode_info, attribute,
                            getattr(flac_info, attribute))
                except:
                    continue
        transcode_info.save()

        self.cv.acquire()
        self.cv.notify_all()
        self.cv.release()

        return 0
Exemple #24
0
def transcode(flac_dir, codec, max_threads=cpu_count(), output_dir=None):
    '''transcode a directory of FLACs to another format'''
    if codec not in encoders.keys():
        return None

    flac_dir = os.path.abspath(flac_dir)
    flac_files = []
    log_files = []
    images = []

    # classify the files
    for path, dirs, files in os.walk(flac_dir, topdown=False):
        for name in files:
            canonical = os.path.join(path, name)
            if fnmatch.fnmatch(name, '*.flac'):
                flac_files.append(canonical)
            elif fnmatch.fnmatch(name, '*.log'):
                log_files.append(canonical)
            elif fnmatch.fnmatch(name, '*.jpg'):
                images.append(canonical)

    # determine sample rate & bits per sample
    flac_info = mediafile.MediaFile(flac_files[0])
    sample_rate = flac_info.mgfile.info.sample_rate
    bits_per_sample = flac_info.mgfile.info.bits_per_sample

    # check if we need to dither to 16/44
    if sample_rate > 48000 or bits_per_sample > 16:
        dither = True
    else:
        dither = False

    # check if we need to encode
    if dither == False and codec == 'FLAC':
        return flac_dir

    # make a new directory for the transcoded files
    if output_dir is not None:
        transcode_dir = output_dir
    else:
        transcode_dir = get_transcode_dir(flac_dir, codec, dither, output_dir)
    if not os.path.exists(transcode_dir):
        os.makedirs(transcode_dir)

    # create transcoding threads
    threads = []
    cv = threading.Condition()
    for flac_file in flac_files:
        cv.acquire()
        while threading.active_count() == (max_threads + 1):
            cv.wait()
        cv.release()
        t = Transcode(flac_file, flac_dir, transcode_dir, codec, dither, cv)
        t.start()
        threads.append(t)

    for t in threads:
        t.join()

    # copy other files
    for path, dirs, files in os.walk(flac_dir, topdown=False):
        for name in files:
            if not fnmatch.fnmatch(name, '*.flac') and not fnmatch.fnmatch(
                    name, '*.m3u'):
                d = re.sub(re.escape(flac_dir), transcode_dir, path)
                if not os.path.exists(d):
                    os.makedirs(d)
                shutil.copy(os.path.join(path, name), d)

    return transcode_dir
 def setUp(self):
     super(TypeTest, self).setUp()
     path = os.path.join(_common.RSRC, b'full.mp3')
     self.mf = mediafile.MediaFile(path)
 def test_read(self):
     path = os.path.join(_common.RSRC, b'empty.flac')
     mf = mediafile.MediaFile(path)
     mf.mgfile.tags[self.key] = "don't"
     self.assertEqual("don't", mf.read_only_test)
 def _mediafile_fixture(self, name, extension='mp3'):
     name = bytestring_path(name + '.' + extension)
     src = os.path.join(_common.RSRC, name)
     target = os.path.join(self.temp_dir, name)
     shutil.copy(src, target)
     return mediafile.MediaFile(target)
 def test_soundcheck_non_ascii(self):
     # Make sure we don't crash when the iTunes SoundCheck field contains
     # non-ASCII binary data.
     f = mediafile.MediaFile(
         os.path.join(_common.RSRC, b'soundcheck-nonascii.m4a'))
     self.assertEqual(f.rg_track_gain, 0.0)
 def test_old_ape_version_bitrate(self):
     media_file = os.path.join(_common.RSRC, b'oldape.ape')
     f = mediafile.MediaFile(media_file)
     self.assertEqual(f.bitrate, 0)
 def test_tempo_with_bpm(self):
     # Some files have a string like "128 BPM" in the tempo field
     # rather than just a number.
     f = mediafile.MediaFile(os.path.join(_common.RSRC, b'bpm.mp3'))
     self.assertEqual(f.bpm, 128)