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)
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)
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)
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)
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
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)
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()
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
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')
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')
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)
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()
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
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
def __try_load_tag(self, path): try: return mediafile.MediaFile(path) except mediafile.UnreadableFileError: return None
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
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)