def handleDone(self): if self._num_loaded == 0: super(StatisticsPlugin, self).handleDone() return print() for stat in self._stats + [self._rules_stat]: stat.report() print() # Detailed rule violations if self.args.verbose: for path in self._rules_log: printMsg(path) # does the right thing for unicode for score, text in self._rules_log[path]: print("\t%s%s%s (%s)" % (Fore.RED, str(score).center(3), Fore.RESET, text)) def prettyScore(): score = float(self._score_sum) / float(self._score_count) if score > 80: color = Fore.GREEN elif score > 70: color = Fore.YELLOW else: color = Fore.RED return (score, color) score, color = prettyScore() print("%sScore%s = %s%d%%%s" % (Style.BRIGHT, Style.RESET_BRIGHT, color, score, Fore.RESET)) if not self.args.verbose: print("Run with --verbose to see files and their rule violations") print()
def handleDone(self): if self._num_loaded == 0: super(StatisticsPlugin, self).handleDone() return print() for stat in self._stats + [self._rules_stat]: stat.report() print() # Detailed rule violations if self.args.verbose: for path in self._rules_log: printMsg(path) # does the right thing for unicode for score, text in self._rules_log[path]: print(f"\t{Fore.RED}{str(score).center(3)}{Fore.RESET} ({text})") def prettyScore(): s = float(self._score_sum) / float(self._score_count) if s > 80: c = Fore.GREEN elif s > 70: c = Fore.YELLOW else: c = Fore.RED return s, c score, color = prettyScore() print(f"{Style.BRIGHT}Score{Style.RESET_BRIGHT} = {color}{score}%%{Fore.RESET}") if not self.args.verbose: print("Run with --verbose to see files and their rule violations") print()
def printHeader(self, file_path): from stat import ST_SIZE file_size = os.stat(file_path)[ST_SIZE] size_str = utils.formatSize(file_size) printMsg("%s\t%s[ %s ]%s" % (boldText(os.path.basename(file_path), c=HEADER_COLOR()), HEADER_COLOR(), size_str, Fore.RESET))
def _getOne(self, key, values, default=None, Type=UnicodeType, required=True): values = set(values) if None in values: values.remove(None) if len(values) != 1: printMsg(u"Detected %s %s names%s" % ( "0" if len(values) == 0 else "multiple", key, "." if not values else (":\n\t%s" % "\n\t".join([compat.unicode(v) for v in values])), )) value = prompt(u"Enter %s" % key.title(), default=default, type_=Type, required=required) else: value = values.pop() return value
def printAudioInfo(info): if isinstance(info, classic.mp3.Mp3AudioInfo): printMsg( classic.boldText(' Time: ') + '%s\tMPEG%d, Layer %s\t[ %s @ %s Hz - %s ]' % (classic.utils.formatTime(info.time_secs), info.mp3_header.version, 'I' * info.mp3_header.layer, info.bit_rate_str, info.mp3_header.sample_freq, info.mp3_header.mode)) printMsg('-' * DummyPlugin.terminal_width)
def test_printMsg(): eyed3.utils.console.USE_ANSI = False with RedirectStdStreams() as out: printMsg("EYEHATEGOD") assert (out.stdout.read() == "EYEHATEGOD\n") eyed3.utils.console.USE_ANSI = True with RedirectStdStreams() as out: printMsg("EYEHATEGOD") assert (out.stdout.read() == "EYEHATEGOD\n")
def printAudioInfo(self, info): if isinstance(info, mp3.Mp3AudioInfo): printMsg(boldText("Time: ") + "%s\tMPEG%d, Layer %s\t[ %s @ %s Hz - %s ]" % (utils.formatTime(info.time_secs), info.mp3_header.version, "I" * info.mp3_header.layer, info.bit_rate_str, info.mp3_header.sample_freq, info.mp3_header.mode)) printMsg("-" * 79)
def test_printMsg(): eyed3.utils.console.USE_ANSI = False with RedirectStdStreams() as out: printMsg("EYEHATEGOD") assert_equal(out.stdout.read(), "EYEHATEGOD\n") eyed3.utils.console.USE_ANSI = True with RedirectStdStreams() as out: printMsg("EYEHATEGOD") assert_equal(out.stdout.read(), "EYEHATEGOD\n")
def handleFile(self, f): parse_version = self.args.tag_version super(ClassicPlugin, self).handleFile(f, tag_version=parse_version) if not self.audio_file: return self.printHeader(f) printMsg("-" * 79) new_tag = False if (not self.audio_file.tag or self.handleRemoves(self.audio_file.tag)): self.audio_file.initTag(version=parse_version) new_tag = True save_tag = (self.handleEdits(self.audio_file.tag) or self.args.force_update or self.args.convert_version) self.printAudioInfo(self.audio_file.info) if not save_tag and new_tag: printError("No ID3 %s tag found!" % id3.versionToString(self.args.tag_version)) return self.printTag(self.audio_file.tag) if save_tag: # Use current tag version unless a convert was supplied version = (self.args.convert_version or self.audio_file.tag.version) printWarning("Writing ID3 version %s" % id3.versionToString(version)) self.audio_file.tag.save( version=version, encoding=self.args.text_encoding, backup=self.args.backup, preserve_file_time=self.args.preserve_file_time) if self.args.rename_pattern: # Handle file renaming. from eyed3.id3.tag import TagTemplate template = TagTemplate(self.args.rename_pattern) name = template.substitute(self.audio_file.tag, zeropad=True) orig = self.audio_file.path try: self.audio_file.rename(name) printWarning("Renamed '%s' to '%s'" % (orig, self.audio_file.path)) except IOError as ex: printError(ex.message) printMsg("-" * 79)
def printHeader(file_path, new_name=None): file_len = len(file_path) from stat import ST_SIZE file_size = os.stat(file_path)[ST_SIZE] size_str = classic.utils.formatSize(file_size) size_len = len(size_str) + 5 if file_len + size_len >= DummyPlugin.terminal_width: file_path = '...' + file_path[-(75 - size_len):] file_len = len(file_path) pat_len = DummyPlugin.terminal_width - file_len - size_len printMsg( '%s%s%s[ %s ]%s' % (classic.boldText(file_path, c=classic.HEADER_COLOR()), classic.HEADER_COLOR(), ' ' * pat_len, size_str, classic.Fore.RESET))
def tagFile(self, filename, track_num, artist, title): # TODO extract GENRE # if GENRE: # validate GENRE # force removal of existing tag if self.args.force is True and self.audio_file.tag is not None: id3.Tag.remove(self.audio_file.path, id3.ID3_ANY_VERSION) printMsg("Tag removed from '{}'".format(filename)) # initialize if self.audio_file.tag is None: self.audio_file.initTag(id3.ID3_V2_4) # set the remainder of the tags self.audio_file.tag.track_num = track_num self.audio_file.tag.title = title self.audio_file.tag.artist = artist self.audio_file.tag.album_artist = self.meta['album_artist'] self.audio_file.tag.album = self.meta['album'] self.audio_file.tag.track_num = (track_num, self.meta['total_num_tracks']) self.audio_file.tag.recording_date = core.Date(self.meta['year']) self.audio_file.tag.tagging_date = '{:%Y-%m-%d}'.format(datetime.datetime.now()) # set a tag to say this was ripped with EAC if self.args.eac is True: self.audio_file.tag.user_text_frames.set('EAC', 'Ripping Tool') # write a cover art image if self.meta['image'] is not None: self.audio_file.tag.images.set( type_=id3.frames.ImageFrame.FRONT_COVER, img_data=self.meta['image'], mime_type='image/jpeg', ) # save the tag self.audio_file.tag.save( version=id3.ID3_V2_4, encoding='utf8', preserve_file_time=True, ) # use classic plugin to print nice output self.classic.terminal_width = getTtySize()[1] self.classic.printHeader(self.audio_file.path) printMsg("-" * self.classic.terminal_width) self.classic.printAudioInfo(self.audio_file.info) self.classic.printTag(self.audio_file.tag)
def _getOne(self, key, values, default=None, Type=unicode, required=True): values = set(values) if None in values: values.remove(None) if len(values) != 1: printMsg( u"Detected %s %s names%s" % ("0" if len(values) == 0 else "multiple", key, "." if not values else (":\n\t%s" % "\n\t".join([compat.unicode(v) for v in values])), )) value = prompt(u"Enter %s" % key.title(), default=default, type_=Type, required=required) else: value = values.pop() return value
def handleDirectory(self, d, _): global md5_file_cache md5_file_cache.clear() try: if not self._file_cache: print("%s: nothing to do." % d) return printMsg("\nProcessing %s" % d) # File images dir_art = [] for img_file in self._dir_images: img_base = os.path.basename(img_file) art_file = ArtFile(img_file) try: pil_img = pilImage(img_file) except IOError as ex: printWarning(compat.unicode(ex)) continue if art_file.art_type: printMsg("file %s: %s\n\t%s" % (img_base, art_file.art_type, pilImageDetails(pil_img))) dir_art.append(art_file) else: printMsg("file %s: unknown (ignored)" % img_base) if not dir_art: print("No art files found.") self._retval += 1 # Tag images all_tags = sorted([f.tag for f in self._file_cache], key=lambda x: x.file_info.name) for tag in all_tags: file_base = os.path.basename(tag.file_info.name) for img in tag.images: try: pil_img = pilImage(img) except IOError as ex: printWarning(compat.unicode(ex)) continue if img.picture_type in art.FROM_ID3_ART_TYPES: img_type = art.FROM_ID3_ART_TYPES[img.picture_type] printMsg("tag %s: %s (Description: %s)\n\t%s" % (file_base, img_type, img.description, pilImageDetails(pil_img))) if self.args.update_files: assert(not self.args.update_tags) path = os.path.dirname(tag.file_info.name) if img.description.startswith(DESCR_FNAME_PREFIX): # Use filename from Image description fname = img.description[ len(DESCR_FNAME_PREFIX):].strip() fname = os.path.splitext(fname)[0] else: fname = art.FILENAMES[img_type][0].strip("*") fname = img.makeFileName(name=fname) if (md5File(os.path.join(path, fname)) == md5Data(img.image_data)): printMsg("Skipping writing of %s, file " "exists and is exactly the same." % img_file) else: img_file = makeUniqueFileName( os.path.join(path, fname), uniq=img.description) printWarning("Writing %s..." % img_file) with open(img_file, "wb") as fp: fp.write(img.image_data) else: printMsg("tag %s: unhandled image type %d (ignored)" % (file_base, img.picture_type)) # Copy file art to tags. if self.args.update_tags: assert(not self.args.update_files) for tag in all_tags: for art_file in dir_art: descr = "filename: %s" % \ os.path.splitext( os.path.basename(art_file.file_path))[0] tag.images.set(art_file.id3_art_type, art_file.image_data, art_file.mime_type, description=descr) tag.save() finally: # Cleans up... super(ArtPlugin, self).handleDirectory(d, _)
def handleFile(self, f): super(Xep118Plugin, self).handleFile(f) if self.audio_file and self.audio_file.tag: xml = self.getXML(self.audio_file) printMsg(xml)
def handleDirectory(self, directory, _): if not self._file_cache: return directory = os.path.abspath(directory) print("\n" + Style.BRIGHT + Fore.GREY + "Scanning directory%s %s" % (Style.RESET_ALL, directory)) def _path(af): return af.path self._handled_one = True # Make sure all of the audio files has a tag. for f in self._file_cache: if f.tag is None: f.initTag() audio_files = sorted(list(self._file_cache), key=_path) self._file_cache = [] edited_files = set() # Check for corrections to LP, EP, COMP if (self.args.dir_type in (LP_TYPE, COMP_TYPE) and len(audio_files) < EP_MAX_HINT): # Do you want EP? if prompt("Only %d audio files, process directory as an EP" % len(audio_files), default=True): self.args.dir_type = EP_TYPE elif (self.args.dir_type in (EP_TYPE, DEMO_TYPE) and len(audio_files) > EP_MAX_HINT): # Do you want LP? if prompt("%d audio files is large for type %s, process " "directory as an LP" % (len(audio_files), self.args.dir_type), default=True): self.args.dir_type = LP_TYPE last = defaultdict(lambda: None) album_artist = None artists = set() album = None if self.args.dir_type != SINGLE_TYPE: album_artist, artists = self._resolveArtistInfo(audio_files) print(Fore.BLUE + u"Album artist: " + Style.RESET_ALL + (album_artist or u"")) print(Fore.BLUE + "Artist" + ("s" if len(artists) > 1 else "") + ": " + Style.RESET_ALL + u", ".join(artists)) album = self._getAlbum(audio_files) print(Fore.BLUE + "Album: " + Style.RESET_ALL + album) rel_date, orel_date, rec_date = self._getDates(audio_files) for what, d in [("Release", rel_date), ("Original", orel_date), ("Recording", rec_date)]: print(Fore.BLUE + ("%s date: " % what) + Style.RESET_ALL + str(d)) num_audio_files = len(audio_files) track_nums = set([f.tag.track_num[0] for f in audio_files]) fix_track_nums = set(range(1, num_audio_files + 1)) != track_nums new_track_nums = [] dir_type = self.args.dir_type for f in sorted(audio_files, key=_path): print(Style.BRIGHT + Fore.GREEN + u"Checking" + Fore.RESET + Fore.GREY + (" %s" % os.path.basename(f.path)) + Style.RESET_ALL) if not f.tag: print("\tAdding new tag") f.initTag() edited_files.add(f) tag = f.tag if tag.version != ID3_V2_4: print("\tConverting to ID3 v2.4") tag.version = ID3_V2_4 edited_files.add(f) if (dir_type != SINGLE_TYPE and album_artist != tag.album_artist): print(u"\tSetting album artist: %s" % album_artist) tag.album_artist = album_artist edited_files.add(f) if not tag.artist and dir_type in (VARIOUS_TYPE, SINGLE_TYPE): # Prompt artist tag.artist = self.prompt("Artist name", default=last["artist"]) last["artist"] = tag.artist elif len(artists) == 1 and tag.artist != artists[0]: assert(dir_type != SINGLE_TYPE) print(u"\tSetting artist: %s" % artists[0]) tag.artist = artists[0] edited_files.add(f) if tag.album != album and dir_type != SINGLE_TYPE: print(u"\tSetting album: %s" % album) tag.album = album edited_files.add(f) orig_title = tag.title if not tag.title: tag.title = prompt("Track title") tag.title = tag.title.strip() if self.args.fix_case: tag.title = _fixCase(tag.title) if orig_title != tag.title: print(u"\tSetting title: %s" % tag.title) edited_files.add(f) if dir_type != SINGLE_TYPE: # Track numbers tnum, ttot = tag.track_num update = False if ttot != num_audio_files: update = True ttot = num_audio_files if fix_track_nums or not (1 <= tnum <= num_audio_files): tnum = None while tnum is None: tnum = int(prompt("Track #", type_=int)) if not (1 <= tnum <= num_audio_files): print(Fore.RED + "Out of range: " + Fore.RESET + "1 <= %d <= %d" % (tnum, num_audio_files)) tnum = None elif tnum in new_track_nums: print(Fore.RED + "Duplicate value: " + Fore.RESET + str(tnum)) tnum = None else: update = True new_track_nums.append(tnum) if update: tag.track_num = (tnum, ttot) print("\tSetting track numbers: %s" % str(tag.track_num)) edited_files.add(f) else: # Singles if tag.track_num != (None, None): tag.track_num = (None, None) edited_files.add(f) if dir_type != SINGLE_TYPE: # Dates if tag.recording_date != rec_date: print("\tSetting %s date (%s)" % ("recording", str(rec_date))) tag.recording_date = rec_date edited_files.add(f) if tag.release_date != rel_date: print("\tSetting %s date (%s)" % ("release", str(rel_date))) tag.release_date = rel_date edited_files.add(f) if tag.original_release_date != orel_date: print("\tSetting %s date (%s)" % ("original release", str(orel_date))) tag.original_release_date = orel_date edited_files.add(f) for fid in ("USER", "PRIV"): n = len(tag.frame_set[fid] or []) if n: print("\tRemoving %d %s frames..." % (n, fid)) del tag.frame_set[fid] edited_files.add(f) # Add TLEN tlen = tag.getTextFrame("TLEN") real_tlen = f.info.time_secs * 1000 if tlen is None or int(tlen) != real_tlen: print("\tSetting TLEN (%d)" % real_tlen) tag.setTextFrame("TLEN", unicode(real_tlen)) edited_files.add(f) # Add custom album type if special and otherwise not able to be # determined. curr_type = tag.album_type if curr_type != dir_type: if dir_type in (LP_TYPE, VARIOUS_TYPE): if curr_type is not None: print("\tClearing %s = %s" % (TXXX_ALBUM_TYPE, curr_type)) tag.album_type = None edited_files.add(f) # We don't set lp because it is the default, and various # can be determined. else: print("\tSetting %s = %s" % (TXXX_ALBUM_TYPE, dir_type)) tag.album_type = dir_type edited_files.add(f) if not self._checkCoverArt(directory, audio_files): if not prompt("Proceed without valid cover file", default=True): return # Determine other changes, like file and/or directory renames # so they can be reported before save confirmation. # File renaming file_renames = [] if self.args.file_rename_pattern: format_str = self.args.file_rename_pattern else: if dir_type == SINGLE_TYPE: format_str = SINGLE_FNAME_FORMAT elif dir_type == VARIOUS_TYPE: format_str = VARIOUS_FNAME_FORMAT else: format_str = NORMAL_FNAME_FORMAT for f in audio_files: orig_name, orig_ext = os.path.splitext(os.path.basename(f.path)) new_name = TagTemplate(format_str).substitute(f.tag, zeropad=True) if orig_name != new_name: printMsg(u"Rename file to %s%s" % (new_name, orig_ext)) file_renames.append((f, new_name, orig_ext)) # Directory renaming dir_rename = None if dir_type != SINGLE_TYPE: if self.args.dir_rename_pattern: dir_format = self.args.dir_rename_pattern else: if dir_type == LIVE_TYPE: dir_format = LIVE_DNAME_FORMAT else: dir_format = NORMAL_DNAME_FORMAT template = TagTemplate(dir_format, dotted_dates=self.args.dotted_dates) pref_dir = template.substitute(audio_files[0].tag, zeropad=True) if os.path.basename(directory) != pref_dir: new_dir = os.path.join(os.path.dirname(directory), pref_dir) printMsg("Rename directory to %s" % new_dir) dir_rename = (directory, new_dir) if not self.args.dry_run: confirmed = False if (edited_files or file_renames or dir_rename): confirmed = prompt("\nSave changes", default=True) if confirmed: for f in edited_files: print(u"Saving %s" % os.path.basename(f.path)) f.tag.save(version=ID3_V2_4, preserve_file_time=True) for f, new_name, orig_ext in file_renames: printMsg(u"Renaming file to %s%s" % (new_name, orig_ext)) f.rename(new_name, preserve_file_time=True) if dir_rename: printMsg("Renaming directory to %s" % dir_rename[1]) s = os.stat(dir_rename[0]) os.rename(dir_rename[0], dir_rename[1]) # With a rename use the origianl access time os.utime(dir_rename[1], (s.st_atime, s.st_atime)) else: printMsg("\nNo changes made (run without -n/--dry-run)")
def _verbose(self, s): if self.args.verbose: printMsg(s)
def handleDirectory(self, d, _): global md5_file_cache md5_file_cache.clear() try: if not self._file_cache: print("%s: nothing to do." % d) return printMsg("\nProcessing %s" % d) # File images dir_art = [] for img_file in self._dir_images: img_base = os.path.basename(img_file) art_file = ArtFile(img_file) try: pil_img = pilImage(img_file) except IOError as ex: printWarning(unicode(ex)) continue if art_file.art_type: printMsg("file %s: %s\n\t%s" % (img_base, art_file.art_type, pilImageDetails(pil_img))) dir_art.append(art_file) else: printMsg("file %s: unknown (ignored)" % img_base) if not dir_art: print("No art files found.") self._retval += 1 # Tag images all_tags = sorted([f.tag for f in self._file_cache], key=lambda x: x.file_info.name) for tag in all_tags: file_base = os.path.basename(tag.file_info.name) for img in tag.images: try: pil_img = pilImage(img) except IOError as ex: printWarning(unicode(ex)) continue if img.picture_type in art.FROM_ID3_ART_TYPES: img_type = art.FROM_ID3_ART_TYPES[img.picture_type] printMsg("tag %s: %s (Description: %s)\n\t%s" % (file_base, img_type, img.description, pilImageDetails(pil_img))) if self.args.update_files: assert(not self.args.update_tags) path = os.path.dirname(tag.file_info.name) if img.description.startswith(DESCR_FNAME_PREFIX): # Use filename from Image description fname = img.description[ len(DESCR_FNAME_PREFIX):].strip() fname = os.path.splitext(fname)[0] else: fname = art.FILENAMES[img_type][0].strip("*") fname = img.makeFileName(name=fname) if (md5File(os.path.join(path, fname)) == md5Data(img.image_data)): printMsg("Skipping writing of %s, file " "exists and is exactly the same." % img_file) else: img_file = makeUniqueFileName( os.path.join(path, fname), uniq=img.description) printWarning("Writing %s..." % img_file) with open(img_file, "wb") as fp: fp.write(img.image_data) else: printMsg("tag %s: unhandled image type %d (ignored)" % (file_base, img.picture_type)) # Copy file art to tags. if self.args.update_tags: assert(not self.args.update_files) for tag in all_tags: for art_file in dir_art: descr = "filename: %s" % \ os.path.splitext( os.path.basename(art_file.file_path))[0] tag.images.set(art_file.id3_art_type, art_file.image_data, art_file.mime_type, description=descr) tag.save() finally: # Cleans up... super(ArtPlugin, self).handleDirectory(d, _)
def printHeader(self, file_path): w = getTtySize()[1] printMsg(self._getFileHeader(file_path, w)) printMsg(self._getHardRule(w))
def handleDone(self): if not self.albums: printMsg(u"No albums found.") return for album in self.albums: audio_files = self.albums[album] if not audio_files: continue audio_files.sort(key=lambda af: af.tag.track_num) max_title_len = 0 avg_bitrate = 0 encoder_info = '' for audio_file in audio_files: tag = audio_file.tag # Compute maximum title length title_len = len(tag.title) if title_len > max_title_len: max_title_len = title_len # Compute average bitrate avg_bitrate += audio_file.info.bit_rate[1] # Grab the last lame version in case not all files have one if "encoder_version" in audio_file.info.lame_tag: version = audio_file.info.lame_tag['encoder_version'] encoder_info = (version or encoder_info) avg_bitrate = avg_bitrate / len(audio_files) printMsg("") printMsg("Artist : %s" % audio_files[0].tag.artist) printMsg("Album : %s" % album) printMsg("Released : %s" % (audio_files[0].tag.original_release_date or audio_files[0].tag.release_date)) printMsg("Recorded : %s" % audio_files[0].tag.recording_date) genre = audio_files[0].tag.genre if genre: genre = genre.name else: genre = "" printMsg("Genre : %s" % genre) printMsg("") printMsg("Source : ") printMsg("Encoder : %s" % encoder_info) printMsg("Codec : mp3") printMsg("Bitrate : ~%s K/s @ %s Hz, %s" % (avg_bitrate, audio_files[0].info.sample_freq, audio_files[0].info.mode)) printMsg("Tag : ID3 %s" % versionToString(audio_files[0].tag.version)) printMsg("") printMsg("Ripped By: ") printMsg("") printMsg("Track Listing") printMsg("-------------") count = 0 total_time = 0 total_size = 0 for audio_file in audio_files: tag = audio_file.tag count += 1 title = tag.title title_len = len(title) padding = " " * ((max_title_len - title_len) + 3) time_secs = audio_file.info.time_secs total_time += time_secs total_size += audio_file.info.size_bytes zero_pad = "0" * (len(str(len(audio_files))) - len(str(count))) printMsg( " %s%d. %s%s(%s)" % (zero_pad, count, title, padding, formatTime(time_secs))) printMsg("") printMsg("Total play time : %s" % formatTime(total_time)) printMsg("Total size : %s" % formatSize(total_size)) printMsg("") printMsg("=" * 78) printMsg(".NFO file created with eyeD3 %s on %s" % (VERSION, time.asctime())) printMsg("For more information about eyeD3 go to %s" % "http://eyeD3.nicfit.net/") printMsg("=" * 78)
def tagFile(self, filename): # get the filesystem character encoding fs_encoding = sys.getfilesystemencoding() parts = os.path.splitext(filename)[0].split(' - ') if self.args.compilation: # compilations are named "01i - Artist Name - Track Name" track_num = parts[0] artist = parts[1] title = parts[2] else: # normal albums are named "01 - Track Name" track_num = parts[0] artist = self.meta['artist'] title = parts[1] # extract GENRE # if GENRE: # validate GENRE # force removal of existing tag if self.args.force is True and self.audio_file.tag is not None: id3.Tag.remove(self.audio_file.path, id3.ID3_ANY_VERSION) printMsg("Tag removed from '{}'".format(filename)) # initialize if self.audio_file.tag is None: self.audio_file.initTag(id3.ID3_V2_4) # decode from filesystem encoding where necessary if type(artist) is str: self.audio_file.tag.artist = artist.decode(fs_encoding) else: self.audio_file.tag.artist = artist if type(self.meta['album_artist']) is str: self.audio_file.tag.album_artist = self.meta['album_artist'].decode(fs_encoding) else: self.audio_file.tag.album_artist = self.meta['album_artist'] if type(self.meta['album']) is str: self.audio_file.tag.album = self.meta['album'].decode(fs_encoding) else: self.audio_file.tag.album = self.meta['album'] # set the remainder of the tags self.audio_file.tag.title = title self.audio_file.tag.track_num = (track_num, self.meta['total_num_tracks']) self.audio_file.tag.recording_date = core.Date(self.meta['year']) self.audio_file.tag.tagging_date = '{:%Y-%m-%d}'.format(datetime.datetime.now()) # set a tag to say this was ripped with EAC if self.args.eac is True: self.audio_file.tag.user_text_frames.set(u'EAC', u'Ripping Tool') # write a cover art image if self.meta['image'] is not None: self.audio_file.tag.images.set( type_=id3.frames.ImageFrame.FRONT_COVER, img_data=self.meta['image'], mime_type='image/jpeg', ) # save the tag self.audio_file.tag.save( version=id3.ID3_V2_4, encoding='utf8', preserve_file_time=True, ) # print output from the classic plugin self.classic.printHeader(self.audio_file.path) printMsg("-" * 79) self.classic.printAudioInfo(self.audio_file.info) self.classic.printTag(self.audio_file.tag)
def printTag(self, tag): if isinstance(tag, id3.Tag): if self.args.quiet: printMsg("ID3 %s: %d frames" % (id3.versionToString(tag.version), len(tag.frame_set))) return printMsg("ID3 %s:" % id3.versionToString(tag.version)) artist = tag.artist if tag.artist else u"" title = tag.title if tag.title else u"" album = tag.album if tag.album else u"" printMsg("%s: %s" % (boldText("title"), title)) printMsg("%s: %s" % (boldText("artist"), artist)) printMsg("%s: %s" % (boldText("album"), album)) for date, date_label in [ (tag.release_date, "release date"), (tag.original_release_date, "original release date"), (tag.recording_date, "recording date"), (tag.encoding_date, "encoding date"), (tag.tagging_date, "tagging date"), ]: if date: printMsg("%s: %s" % (boldText(date_label), str(date))) track_str = "" (track_num, track_total) = tag.track_num if track_num is not None: track_str = str(track_num) if track_total: track_str += "/%d" % track_total genre = tag.genre genre_str = "%s: %s (id %s)" % (boldText("genre"), genre.name, str(genre.id)) if genre else u"" printMsg("%s: %s\t\t%s" % (boldText("track"), track_str, genre_str)) disc_str = "" (num, total) = tag.disc_num if num is not None: disc_str = str(num) if total: disc_str += "/%d" % total printMsg("%s: %s" % (boldText("disc"), disc_str)) # PCNT play_count = tag.play_count if tag.play_count is not None: printMsg("%s %d" % (boldText("Play Count:"), play_count)) # POPM for popm in tag.popularities: printMsg("%s [email: %s] [rating: %d] [play count: %d]" % (boldText("Popularity:"), popm.email, popm.rating, popm.count)) # TBPM bpm = tag.bpm if bpm is not None: printMsg("%s %d" % (boldText("BPM:"), bpm)) # TPUB pub = tag.publisher if pub is not None: printMsg("%s %s" % (boldText("Publisher/label:"), pub)) # UFID for ufid in tag.unique_file_ids: printMsg("%s [%s] : %s" % \ (boldText("Unique File ID:"), ufid.owner_id, ufid.uniq_id.encode("string_escape"))) # COMM for c in tag.comments: printMsg("%s: [Description: %s] [Lang: %s]\n%s" % (boldText("Comment"), c.description or "", c.lang or "", c.text or "")) # USLT for l in tag.lyrics: printMsg("%s: [Description: %s] [Lang: %s]\n%s" % (boldText("Lyrics"), l.description or u"", l.lang or "", l.text)) # TXXX for f in tag.user_text_frames: printMsg("%s: [Description: %s]\n%s" % (boldText("UserTextFrame"), f.description, f.text)) # URL frames for desc, url in ( ("Artist URL", tag.artist_url), ("Audio source URL", tag.audio_source_url), ("Audio file URL", tag.audio_file_url), ("Internet radio URL", tag.internet_radio_url), ("Commercial URL", tag.commercial_url), ("Payment URL", tag.payment_url), ("Publisher URL", tag.publisher_url), ("Copyright URL", tag.copyright_url), ): if url: printMsg("%s: %s" % (boldText(desc), url)) # user url frames for u in tag.user_url_frames: printMsg("%s [Description: %s]: %s" % (u.id, u.description, u.url)) # APIC for img in tag.images: if img.mime_type != ImageFrame.URL_MIME_TYPE: printMsg("%s: [Size: %d bytes] [Type: %s]" % (boldText(img.picTypeToString(img.picture_type) + " Image"), len(img.image_data), img.mime_type)) printMsg("Description: %s" % img.description) printMsg("") if self.args.write_images_dir: img_path = "%s%s" % (self.args.write_images_dir, os.sep) if not os.path.isdir(img_path): raise IOError("Directory does not exist: %s" % img_path) img_file = self._getDefaultNameForImage(img) count = 1 while os.path.exists(os.path.join(img_path, img_file)): img_file = self._getDefaultNameForImage(img, str(count)) count += 1 printWarning("Writing %s..." % os.path.join(img_path, img_file)) with open(os.path.join(img_path, img_file), "wb") as fp: fp.write(img.image_data) else: printMsg("%s: [Type: %s] [URL: %s]" % (boldText(img.picTypeToString(img.picture_type) + " Image"), img.mime_type, img.image_url)) printMsg("Description: %s" % img.description) printMsg("") # GOBJ for obj in tag.objects: printMsg("%s: [Size: %d bytes] [Type: %s]" % (boldText("GEOB"), len(obj.object_data), obj.mime_type)) printMsg("Description: %s" % obj.description) printMsg("Filename: %s" % obj.filename) printMsg("\n") if self.args.write_objects_dir: obj_path = "%s%s" % (self.args.write_objects_dir, os.sep) if not os.path.isdir(obj_path): raise IOError("Directory does not exist: %s" % obj_path) obj_file = self._getDefaultNameForObject(obj) count = 1 while os.path.exists(os.path.join(obj_path, obj_file)): obj_file = self._getDefaultNameForObject(obj, str(count)) count += 1 printWarning("Writing %s..." % os.path.join(obj_path, obj_file)) with open(os.path.join(obj_path, obj_file), "wb") as fp: fp.write(obj.object_data) # PRIV for p in tag.privates: printMsg("%s: [Data: %d bytes]" % (boldText("PRIV"), len(p.data))) printMsg("Owner Id: %s" % p.owner_id) # MCDI if tag.cd_id: printMsg("\n%s: [Data: %d bytes]" % (boldText("MCDI"), len(tag.cd_id))) # USER if tag.terms_of_use: printMsg("\nTerms of Use (%s): %s" % (boldText("USER"), tag.terms_of_use)) if self.args.verbose: printMsg("-" * 79) printMsg("%d ID3 Frames:" % len(tag.frame_set)) for fid in tag.frame_set: num_frames = len(tag.frame_set[fid]) count = " x %d" % num_frames if num_frames > 1 else "" printMsg("%s%s" % (fid, count)) else: raise TypeError("Unknown tag type: " + str(type(tag)))
def handleDone(self): """If no audio files were loaded this simply prints 'Nothing to do'.""" if self._num_loaded == 0: printMsg("Nothing to do")
def handleDone(self): '''If no audio files were loaded this simply prints "Nothing to do".''' if self._num_loaded == 0: printMsg("Nothing to do")
def handleDirectory(self, directory, _): if not self._file_cache: return directory = os.path.abspath(directory) print("\n" + Style.bright + Fore.grey + "Scanning directory%s %s" % (Style.reset_all, directory)) def _path(af): return af.path audio_files = sorted(list(self._file_cache), key=_path) self._file_cache = [] edited_files = set() if (len(audio_files) < EP_MAX_HINT and self.args.dir_type not in (EP_TYPE, DEMO_TYPE, VARIOUS_TYPE)): if _prompt("Only %d audio files, process directory as an EP" % len(audio_files), default=True): self.args.dir_type = EP_TYPE elif self.args.dir_type == EP_TYPE and len(audio_files) > EP_MAX_HINT: if _prompt("%d audio files is large for an EP, process directory " "as an LP" % len(audio_files), default=True): self.args.dir_type = LP_TYPE elif (self.args.dir_type not in (VARIOUS_TYPE, COMP_TYPE, LIVE_TYPE) and len(audio_files) > LP_MAX_HINT): if _prompt("%d audio files is large for an LP, process directory " "as a compilation" % len(audio_files), default=True): self.args.dir_type = COMP_TYPE last = defaultdict(lambda: None) artist = self._getArtist(audio_files) print(Fore.BLUE + "Artist: " + Style.RESET_ALL + (artist or "Various Artists")) album = self._getAlbum(audio_files) print(Fore.BLUE + "Album: " + Style.RESET_ALL + album) rel_date, orel_date, rec_date = self._getDates(audio_files) for what, d in [("Release", rel_date), ("Original", orel_date), ("Recording", rec_date)]: print(Fore.BLUE + ("%s date: " % what) + Style.RESET_ALL + str(d)) num_audio_files = len(audio_files) track_nums = set([f.tag.track_num[0] for f in audio_files]) fix_track_nums = bool(set(range(1, num_audio_files + 1)) != track_nums) new_track_nums = [] for f in sorted(audio_files, key=_path): print(Style.bright + Fore.grey + u"Checking%s %s" % (Style.reset_all, os.path.basename(f.path))) if not f.tag: print("\tAdding new tag") f.initTag() edited_files.add(f) tag = f.tag if tag.version != ID3_V2_4: print("\tConverting to ID3 v2.4") tag.version = ID3_V2_4 edited_files.add(f) if self.args.dir_type == VARIOUS_TYPE: if not tag.artist: tag.artist = self._prompt("Artist name", default=last["artist"]) last["artist"] = tag.artist elif tag.artist != artist: print(u"\tSetting artist: %s" % artist) tag.artist = artist edited_files.add(f) if tag.album != album: print(u"\tSetting album: %s" % album) tag.album = album edited_files.add(f) orig_title = tag.title if not tag.title: tag.title = _prompt("Track title") if self.args.fix_case: tag.title = _fixCase(tag.title) if orig_title != tag.title: print(u"\tSetting title: %s" % tag.title) edited_files.add(f) # Track numbers tnum, ttot = tag.track_num update = False if ttot != num_audio_files: update = True ttot = num_audio_files if fix_track_nums or not (1 <= tnum <= num_audio_files): tnum = None while tnum is None: tnum = int(_prompt("Track #")) if not (1 <= tnum <= num_audio_files): print(Fore.red + "Out of range: " + Fore.reset + "1 <= %d <= %d" % (tnum, num_audio_files)) tnum = None elif tnum in new_track_nums: print(Fore.red + "Duplicate value: " + Fore.reset + str(tnum)) tnum = None else: update = True new_track_nums.append(tnum) if update: tag.track_num = (tnum, ttot) print("\tSetting track numbers: %s" % str(tag.track_num)) edited_files.add(f) # Dates if tag.recording_date != rec_date: print("\tSetting %s date (%s)" % ("recording", str(rec_date))) tag.recording_date = rec_date edited_files.add(f) if tag.release_date != rel_date: print("\tSetting %s date (%s)" % ("release", str(rel_date))) tag.release_date = rel_date edited_files.add(f) if tag.original_release_date != orel_date: print("\tSetting %s date (%s)" % ("original release", str(orel_date))) tag.original_release_date = orel_date edited_files.add(f) for fid in ("USER", "PRIV"): n = len(tag.frame_set[fid] or []) if n: print("\tRemoving %d %s frames..." % (n, fid)) del tag.frame_set[fid] edited_files.add(f) # Add TLEN tlen = tag.getTextFrame("TLEN") real_tlen = f.info.time_secs * 1000 if tlen is None or int(tlen) != real_tlen: print("\tSetting TLEN (%d)" % real_tlen) tag.setTextFrame("TLEN", unicode(real_tlen)) edited_files.add(f) # Add custom album type if special and otherwise not able to be # determined. curr_type = tag.user_text_frames.get(TXXX_ALBUM_TYPE) if curr_type is not None: curr_type = curr_type.text if curr_type != self.args.dir_type: if self.args.dir_type in (LP_TYPE, VARIOUS_TYPE): if curr_type is not None: print("\tClearing %s = %s" % (TXXX_ALBUM_TYPE, curr_type)) tag.user_text_frames.remove(TXXX_ALBUM_TYPE) edited_files.add(f) # We don't set lp because it is the default, and various # can be determined. else: print("\tSetting %s = %s" % (TXXX_ALBUM_TYPE, self.args.dir_type)) tag.user_text_frames.set(self.args.dir_type, TXXX_ALBUM_TYPE) edited_files.add(f) # Determine other changes, like file and/or duirectory renames # so they can be reported before save confirmation. file_renames = [] format_str = (NORMAL_FNAME_FORMAT if self.args.dir_type != VARIOUS_TYPE else VARIOUS_FNAME_FORMAT) for f in audio_files: orig_name, orig_ext = os.path.splitext(os.path.basename(f.path)) new_name = TagTemplate(format_str).substitute(f.tag, zeropad=True) if orig_name != new_name: printMsg(u"Rename file to %s%s" % (new_name, orig_ext)) file_renames.append((f, new_name, orig_ext)) dir_rename = None if self.args.dir_type == LIVE_TYPE: dir_format = LIVE_DNAME_FORMAT else: dir_format = NORMAL_DNAME_FORMAT template = TagTemplate(dir_format, dotted_dates=self.args.dotted_dates) pref_dir = template.substitute(audio_files[0].tag, zeropad=True) if os.path.basename(directory) != pref_dir: new_dir = os.path.join(os.path.dirname(directory), pref_dir) printMsg("Rename directory to %s" % new_dir) dir_rename = (directory, new_dir) if not self.args.dry_run: confirmed = False if (edited_files or file_renames or dir_rename): confirmed = _prompt("\nSave changes", default=True) if confirmed: for f in edited_files: print(u"Saving %s" % os.path.basename(f.path)) f.tag.save(version=ID3_V2_4) for f, new_name, orig_ext in file_renames: printMsg(u"Renaming file to %s%s" % (new_name, orig_ext)) f.rename(new_name) if dir_rename: printMsg("Renaming directory to %s" % dir_rename[1]) os.rename(dir_rename[0], dir_rename[1]) else: printMsg("\nNo changes made (run without -n/--dry-run)")
def handleFile(self, f, *_, **__): super().handleFile(f) if self.audio_file is None: return self.printHeader(f) if (self.audio_file.info is None or not self.audio_file.info.lame_tag): printMsg("No LAME Tag") return lt = self.audio_file.info.lame_tag if "infotag_crc" not in lt: try: printMsg(f"Encoder Version: {lt['encoder_version']}") except KeyError: pass return values = [ ("Encoder Version", lt['encoder_version']), ("LAME Tag Revision", lt['tag_revision']), ("VBR Method", lt['vbr_method']), ("Lowpass Filter", lt['lowpass_filter']), ] if "replaygain" in lt: try: peak = lt["replaygain"]["peak_amplitude"] db = 20 * math.log10(peak) val = "%.8f (%+.1f dB)" % (peak, db) values.append(("Peak Amplitude", val)) except KeyError: pass for type_ in ["radio", "audiofile"]: try: gain = lt["replaygain"][type_] name = "%s Replay Gain" % gain['name'].capitalize() val = "%s dB (%s)" % (gain['adjustment'], gain['originator']) values.append((name, val)) except KeyError: pass values.append(("Encoding Flags", " ".join((lt["encoding_flags"])))) if lt["nogap"]: values.append(("No Gap", " and ".join(lt["nogap"]))) values.append(("ATH Type", lt["ath_type"])) values.append(("Bitrate (%s)" % lt["bitrate"][1], lt["bitrate"][0])) values.append(("Encoder Delay", "%s samples" % lt["encoder_delay"])) values.append( ("Encoder Padding", "%s samples" % lt["encoder_padding"])) values.append(("Noise Shaping", lt["noise_shaping"])) values.append(("Stereo Mode", lt["stereo_mode"])) values.append(("Unwise Settings", lt["unwise_settings"])) values.append(("Sample Frequency", lt["sample_freq"])) values.append( ("MP3 Gain", "%s (%+.1f dB)" % (lt["mp3_gain"], lt["mp3_gain"] * 1.5))) values.append(("Preset", lt["preset"])) values.append(("Surround Info", lt["surround_info"])) values.append(("Music Length", "%s" % formatSize(lt["music_length"]))) values.append(("Music CRC-16", "%04X" % lt["music_crc"])) values.append(("LAME Tag CRC-16", "%04X" % lt["infotag_crc"])) for v in values: printMsg(f"{v[0]:<20}: {v[1]}")
def handleFile(self, f): super(LameInfoPlugin, self).handleFile(f) self.printHeader(f) if not self.audio_file or not self.audio_file.info.lame_tag: printMsg('No LAME Tag') return format = '%-20s: %s' lt = self.audio_file.info.lame_tag if "infotag_crc" not in lt: try: printMsg('%s: %s' % ('Encoder Version', lt['encoder_version'])) except KeyError: pass return values = [] values.append(('Encoder Version', lt['encoder_version'])) values.append(('LAME Tag Revision', lt['tag_revision'])) values.append(('VBR Method', lt['vbr_method'])) values.append(('Lowpass Filter', lt['lowpass_filter'])) if "replaygain" in lt: try: peak = lt['replaygain']['peak_amplitude'] db = 20 * math.log10(peak) val = '%.8f (%+.1f dB)' % (peak, db) values.append(('Peak Amplitude', val)) except KeyError: pass for type in ['radio', 'audiofile']: try: gain = lt['replaygain'][type] name = '%s Replay Gain' % gain['name'].capitalize() val = '%s dB (%s)' % (gain['adjustment'], gain['originator']) values.append((name, val)) except KeyError: pass values.append(('Encoding Flags', ' '.join((lt['encoding_flags'])))) if lt['nogap']: values.append(('No Gap', ' and '.join(lt['nogap']))) values.append(('ATH Type', lt['ath_type'])) values.append(('Bitrate (%s)' % lt['bitrate'][1], lt['bitrate'][0])) values.append(('Encoder Delay', '%s samples' % lt['encoder_delay'])) values.append( ('Encoder Padding', '%s samples' % lt['encoder_padding'])) values.append(('Noise Shaping', lt['noise_shaping'])) values.append(('Stereo Mode', lt['stereo_mode'])) values.append(('Unwise Settings', lt['unwise_settings'])) values.append(('Sample Frequency', lt['sample_freq'])) values.append( ('MP3 Gain', '%s (%+.1f dB)' % (lt['mp3_gain'], lt['mp3_gain'] * 1.5))) values.append(('Preset', lt['preset'])) values.append(('Surround Info', lt['surround_info'])) values.append(('Music Length', '%s' % formatSize(lt['music_length']))) values.append(('Music CRC-16', '%04X' % lt['music_crc'])) values.append(('LAME Tag CRC-16', '%04X' % lt['infotag_crc'])) for v in values: printMsg(format % (v))
def handleDone(self): if not self.albums: printMsg(u"No albums found.") return for album in self.albums: audio_files = self.albums[album] if not audio_files: continue audio_files.sort(key=lambda af: af.tag.track_num) max_title_len = 0 avg_bitrate = 0 encoder_info = '' for audio_file in audio_files: tag = audio_file.tag # Compute maximum title length title_len = len(tag.title) if title_len > max_title_len: max_title_len = title_len # Compute average bitrate avg_bitrate += audio_file.info.bit_rate[1] # Grab the last lame version in case not all files have one if "encoder_version" in audio_file.info.lame_tag: version = audio_file.info.lame_tag['encoder_version'] encoder_info = (version or encoder_info) avg_bitrate = avg_bitrate / len(audio_files) printMsg("") printMsg("Artist : %s" % audio_files[0].tag.artist) printMsg("Album : %s" % album) printMsg("Released : %s" % (audio_files[0].tag.original_release_date or audio_files[0].tag.release_date)) printMsg("Recorded : %s" % audio_files[0].tag.recording_date) genre = audio_files[0].tag.genre if genre: genre = genre.name else: genre = "" printMsg("Genre : %s" % genre) printMsg("") printMsg("Source : ") printMsg("Encoder : %s" % encoder_info) printMsg("Codec : mp3") printMsg("Bitrate : ~%s K/s @ %s Hz, %s" % (avg_bitrate, audio_files[0].info.sample_freq, audio_files[0].info.mode)) printMsg("Tag : ID3 %s" % versionToString(audio_files[0].tag.version)) printMsg("") printMsg("Ripped By: ") printMsg("") printMsg("Track Listing") printMsg("-------------") count = 0 total_time = 0 total_size = 0 for audio_file in audio_files: tag = audio_file.tag count += 1 title = tag.title title_len = len(title) padding = " " * ((max_title_len - title_len) + 3) time_secs = audio_file.info.time_secs total_time += time_secs total_size += audio_file.info.size_bytes zero_pad = "0" * (len(str(len(audio_files))) - len(str(count))) printMsg(" %s%d. %s%s(%s)" % (zero_pad, count, title, padding, formatTime(time_secs))) printMsg("") printMsg("Total play time : %s" % formatTime(total_time)) printMsg("Total size : %s" % formatSize(total_size)) printMsg("") printMsg("=" * 78) printMsg(".NFO file created with eyeD3 %s on %s" % (VERSION, time.asctime())) printMsg("For more information about eyeD3 go to %s" % "http://eyeD3.nicfit.net/") printMsg("=" * 78)
def handleFile(self, f): super(LameInfoPlugin, self).handleFile(f) self.printHeader(f) if not self.audio_file or not self.audio_file.info.lame_tag: printMsg('No LAME Tag') return format = '%-20s: %s' lt = self.audio_file.info.lame_tag if "infotag_crc" not in lt: try: printMsg('%s: %s' % ('Encoder Version', lt['encoder_version'])) except KeyError: pass return values = [] values.append(('Encoder Version', lt['encoder_version'])) values.append(('LAME Tag Revision', lt['tag_revision'])) values.append(('VBR Method', lt['vbr_method'])) values.append(('Lowpass Filter', lt['lowpass_filter'])) if "replaygain" in lt: try: peak = lt['replaygain']['peak_amplitude'] db = 20 * math.log10(peak) val = '%.8f (%+.1f dB)' % (peak, db) values.append(('Peak Amplitude', val)) except KeyError: pass for type in ['radio', 'audiofile']: try: gain = lt['replaygain'][type] name = '%s Replay Gain' % gain['name'].capitalize() val = '%s dB (%s)' % (gain['adjustment'], gain['originator']) values.append((name, val)) except KeyError: pass values.append(('Encoding Flags', ' '.join((lt['encoding_flags'])))) if lt['nogap']: values.append(('No Gap', ' and '.join(lt['nogap']))) values.append(('ATH Type', lt['ath_type'])) values.append(('Bitrate (%s)' % lt['bitrate'][1], lt['bitrate'][0])) values.append(('Encoder Delay', '%s samples' % lt['encoder_delay'])) values.append(('Encoder Padding', '%s samples' % lt['encoder_padding'])) values.append(('Noise Shaping', lt['noise_shaping'])) values.append(('Stereo Mode', lt['stereo_mode'])) values.append(('Unwise Settings', lt['unwise_settings'])) values.append(('Sample Frequency', lt['sample_freq'])) values.append(('MP3 Gain', '%s (%+.1f dB)' % (lt['mp3_gain'], lt['mp3_gain'] * 1.5))) values.append(('Preset', lt['preset'])) values.append(('Surround Info', lt['surround_info'])) values.append(('Music Length', '%s' % formatSize(lt['music_length']))) values.append(('Music CRC-16', '%04X' % lt['music_crc'])) values.append(('LAME Tag CRC-16', '%04X' % lt['infotag_crc'])) for v in values: printMsg(format % (v))
def handleDirectory(self, d, _): global md5_file_cache md5_file_cache.clear() if not self._file_cache: log.debug("%s: nothing to do." % d) return try: all_tags = sorted([f.tag for f in self._file_cache if f.tag], key=lambda x: x.file_info.name) # If not deemed an album, move on. if len(set([t.album for t in all_tags])) > 1: log.debug("Skipping directory '%s', non-album." % d) return printMsg(cformat("\nChecking: ", Fore.BLUE) + d) # File images dir_art = [] for img_file in self._dir_images: img_base = os.path.basename(img_file) art_file = ArtFile(img_file) try: pil_img = pilImage(img_file) except IOError as ex: printWarning(compat.unicode(ex)) continue if art_file.art_type: self._verbose("file %s: %s\n\t%s" % (img_base, art_file.art_type, pilImageDetails(pil_img))) dir_art.append(art_file) else: self._verbose("file %s: unknown (ignored)" % img_base) if not dir_art: print(cformat("NONE", Fore.RED)) self._retval += 1 else: print(cformat("OK", Fore.GREEN)) # --download handling if not dir_art and self.args.download and _have_lastfm: tag = all_tags[0] artists = set([t.artist for t in all_tags]) if len(artists) > 1: artist_query = VARIOUS_ARTISTS else: artist_query = tag.album_artist or tag.artist try: url = getAlbumArt(artist_query, tag.album) resp = requests.get(url) if resp.status_code != 200: raise ValueError() except ValueError: print("Album art download not found") else: print("Downloading album art...") img = pilImage(io.BytesIO(resp.content)) cover = Path(d) / "cover.{}".format(img.format.lower()) assert not cover.exists() img.save(str(cover)) print("Save {cover}".format(cover=cover)) # Tag images for tag in all_tags: file_base = os.path.basename(tag.file_info.name) for img in tag.images: try: pil_img = pilImage(img) pil_img_details = pilImageDetails(pil_img) except (OSError, IOError) as ex: printWarning(compat.unicode(ex)) continue if img.picture_type in art.FROM_ID3_ART_TYPES: img_type = art.FROM_ID3_ART_TYPES[img.picture_type] self._verbose("tag %s: %s (Description: %s)\n\t%s" % (file_base, img_type, img.description, pil_img_details)) if self.args.update_files: assert(not self.args.update_tags) path = os.path.dirname(tag.file_info.name) if img.description.startswith(DESCR_FNAME_PREFIX): # Use filename from Image description fname = img.description[ len(DESCR_FNAME_PREFIX):].strip() fname = os.path.splitext(fname)[0] else: fname = art.FILENAMES[img_type][0].strip("*") fname = img.makeFileName(name=fname) if (md5File(os.path.join(path, fname)) == md5Data(img.image_data)): printMsg("Skipping writing of %s, file " "exists and is exactly the same." % fname) else: img_file = makeUniqueFileName( os.path.join(path, fname), uniq=img.description) printWarning("Writing %s..." % img_file) with open(img_file, "wb") as fp: fp.write(img.image_data) else: self._verbose( "tag %s: unhandled image type %d (ignored)" % (file_base, img.picture_type) ) # Copy file art to tags. if self.args.update_tags: assert(not self.args.update_files) for tag in all_tags: for art_file in dir_art: art_path = os.path.basename(art_file.file_path) printMsg("Copying %s to tag '%s' image" % (art_path, art_file.id3_art_type)) descr = "filename: %s" % os.path.splitext(art_path)[0] tag.images.set(art_file.id3_art_type, art_file.image_data, art_file.mime_type, description=descr) tag.save() finally: # Cleans up... super(ArtPlugin, self).handleDirectory(d, _)
def handleDirectory(self, d, _): global md5_file_cache md5_file_cache.clear() if not self._file_cache: log.debug(f"{d}: nothing to do.") return try: all_tags = sorted([f.tag for f in self._file_cache if f.tag], key=lambda x: x.file_info.name) # If not deemed an album, move on. if len(set([t.album for t in all_tags])) > 1: log.debug(f"Skipping directory '{d}', non-album.") return printMsg(cformat("\nChecking: ", Fore.BLUE) + d) # File images dir_art = [] for img_file in self._dir_images: img_base = os.path.basename(img_file) art_file = ArtFile(img_file) try: pil_img = pilImage(img_file) except IOError as ex: printWarning(str(ex)) continue if art_file.art_type: self._verbose( f"file {img_base}: {art_file.art_type}\n\t{pilImageDetails(pil_img)}") dir_art.append(art_file) else: self._verbose(f"file {img_base}: unknown (ignored)") if not dir_art: print(cformat("NONE", Fore.RED)) self._retval += 1 else: print(cformat("OK", Fore.GREEN)) # --download handling if not dir_art and self.args.download: tag = all_tags[0] artists = set([t.artist for t in all_tags]) if len(artists) > 1: artist_query = VARIOUS_ARTISTS else: artist_query = tag.album_artist or tag.artist try: url = getAlbumArt(artist_query, tag.album) print("Downloading album art...") resp = requests.get(url) if resp.status_code != 200: raise ValueError() except ValueError: print("Album art download not found") else: img = pilImage(io.BytesIO(resp.content)) cover = Path(d) / "cover.{}".format(img.format.lower()) assert not cover.exists() img.save(str(cover)) print("Save {cover}".format(cover=cover)) # Tag images for tag in all_tags: file_base = os.path.basename(tag.file_info.name) for img in tag.images: try: pil_img = pilImage(img) pil_img_details = pilImageDetails(pil_img) except (OSError, IOError) as ex: printWarning(str(ex)) continue if img.picture_type in art.FROM_ID3_ART_TYPES: img_type = art.FROM_ID3_ART_TYPES[img.picture_type] self._verbose("tag %s: %s (Description: %s)\n\t%s" % (file_base, img_type, img.description, pil_img_details)) if self.args.update_files: assert(not self.args.update_tags) path = os.path.dirname(tag.file_info.name) if img.description.startswith(DESCR_FNAME_PREFIX): # Use filename from Image description fname = img.description[ len(DESCR_FNAME_PREFIX):].strip() fname = os.path.splitext(fname)[0] else: fname = art.FILENAMES[img_type][0].strip("*") fname = img.makeFileName(name=fname) if (md5File(os.path.join(path, fname)) == md5Data(img.image_data)): printMsg("Skipping writing of %s, file " "exists and is exactly the same." % fname) else: img_file = makeUniqueFileName( os.path.join(path, fname), uniq=img.description) printWarning("Writing %s..." % img_file) with open(img_file, "wb") as fp: fp.write(img.image_data) else: self._verbose( "tag %s: unhandled image type %d (ignored)" % (file_base, img.picture_type) ) # Copy file art to tags. if self.args.update_tags: assert(not self.args.update_files) for tag in all_tags: for art_file in dir_art: art_path = os.path.basename(art_file.file_path) printMsg("Copying %s to tag '%s' image" % (art_path, art_file.id3_art_type)) descr = "filename: %s" % os.path.splitext(art_path)[0] tag.images.set(art_file.id3_art_type, art_file.image_data, art_file.mime_type, description=descr) tag.save() finally: # Cleans up... super(ArtPlugin, self).handleDirectory(d, _)
def handleDone(self): if not self._handled_one: printMsg("Nothing to do")
def handleDirectory(self, directory, _): if not self._file_cache: return directory = os.path.abspath(directory) print("\n" + Style.BRIGHT + Fore.GREY + "Scanning directory%s %s" % (Style.RESET_ALL, directory)) def _path(af): return af.path self._handled_one = True # Make sure all of the audio files has a tag. for f in self._file_cache: if f.tag is None: f.initTag() audio_files = sorted(list(self._file_cache), key=_path) self._file_cache = [] edited_files = set() self._curr_dir_type = self.args.dir_type if self._curr_dir_type is None: types = set([a.tag.album_type for a in audio_files]) if len(types) == 1: self._curr_dir_type = types.pop() # Check for corrections to LP, EP, COMP if (self._curr_dir_type is None and len(audio_files) < EP_MAX_SIZE_HINT): # Do you want EP? if False in [a.tag.album_type == EP_TYPE for a in audio_files]: if prompt("Only %d audio files, process directory as an EP" % len(audio_files), default=True): self._curr_dir_type = EP_TYPE else: self._curr_dir_type = EP_TYPE elif (self._curr_dir_type in (EP_TYPE, DEMO_TYPE) and len(audio_files) > EP_MAX_SIZE_HINT): # Do you want LP? if prompt("%d audio files is large for type %s, process " "directory as an LP" % (len(audio_files), self._curr_dir_type), default=True): self._curr_dir_type = LP_TYPE last = defaultdict(lambda: None) album_artist = None artists = set() album = None if self._curr_dir_type != SINGLE_TYPE: album_artist, artists = self._resolveArtistInfo(audio_files) print(Fore.BLUE + u"Album artist: " + Style.RESET_ALL + (album_artist or u"")) print(Fore.BLUE + "Artist" + ("s" if len(artists) > 1 else "") + ": " + Style.RESET_ALL + u", ".join(artists)) album = self._getAlbum(audio_files) print(Fore.BLUE + "Album: " + Style.RESET_ALL + album) rel_date, orel_date, rec_date = self._getDates(audio_files) for what, d in [("Release", rel_date), ("Original", orel_date), ("Recording", rec_date)]: print(Fore.BLUE + ("%s date: " % what) + Style.RESET_ALL + str(d)) num_audio_files = len(audio_files) track_nums = set([f.tag.track_num[0] for f in audio_files]) fix_track_nums = set(range(1, num_audio_files + 1)) != track_nums new_track_nums = [] dir_type = self._curr_dir_type for f in sorted(audio_files, key=_path): print(Style.BRIGHT + Fore.GREEN + u"Checking" + Fore.RESET + Fore.GREY + (" %s" % os.path.basename(f.path)) + Style.RESET_ALL) if not f.tag: print("\tAdding new tag") f.initTag() edited_files.add(f) tag = f.tag if tag.version != ID3_V2_4: print("\tConverting to ID3 v2.4") tag.version = ID3_V2_4 edited_files.add(f) if (dir_type != SINGLE_TYPE and album_artist != tag.album_artist): print(u"\tSetting album artist: %s" % album_artist) tag.album_artist = album_artist edited_files.add(f) if not tag.artist and dir_type in (VARIOUS_TYPE, SINGLE_TYPE): # Prompt artist tag.artist = prompt("Artist name", default=last["artist"]) last["artist"] = tag.artist elif len(artists) == 1 and tag.artist != artists[0]: assert(dir_type != SINGLE_TYPE) print(u"\tSetting artist: %s" % artists[0]) tag.artist = artists[0] edited_files.add(f) if tag.album != album and dir_type != SINGLE_TYPE: print(u"\tSetting album: %s" % album) tag.album = album edited_files.add(f) orig_title = tag.title if not tag.title: tag.title = prompt("Track title") tag.title = tag.title.strip() if self.args.fix_case: tag.title = _fixCase(tag.title) if orig_title != tag.title: print(u"\tSetting title: %s" % tag.title) edited_files.add(f) if dir_type != SINGLE_TYPE: # Track numbers tnum, ttot = tag.track_num update = False if ttot != num_audio_files: update = True ttot = num_audio_files if fix_track_nums or not (1 <= tnum <= num_audio_files): tnum = None while tnum is None: tnum = int(prompt("Track #", type_=int)) if not (1 <= tnum <= num_audio_files): print(Fore.RED + "Out of range: " + Fore.RESET + "1 <= %d <= %d" % (tnum, num_audio_files)) tnum = None elif tnum in new_track_nums: print(Fore.RED + "Duplicate value: " + Fore.RESET + str(tnum)) tnum = None else: update = True new_track_nums.append(tnum) if update: tag.track_num = (tnum, ttot) print("\tSetting track numbers: %s" % str(tag.track_num)) edited_files.add(f) else: # Singles if tag.track_num != (None, None): tag.track_num = (None, None) edited_files.add(f) if dir_type != SINGLE_TYPE: # Dates if rec_date and tag.recording_date != rec_date: print("\tSetting %s date (%s)" % ("recording", str(rec_date))) tag.recording_date = rec_date edited_files.add(f) if rel_date and tag.release_date != rel_date: print("\tSetting %s date (%s)" % ("release", str(rel_date))) tag.release_date = rel_date edited_files.add(f) if orel_date and tag.original_release_date != orel_date: print("\tSetting %s date (%s)" % ("original release", str(orel_date))) tag.original_release_date = orel_date edited_files.add(f) for frame in list(tag.frameiter(["USER", "PRIV"])): print("\tRemoving %s frames: %s" % (frame.id, frame.owner_id if frame.id == b"PRIV" else frame.text)) tag.frame_set[frame.id].remove(frame) edited_files.add(f) # Add TLEN tlen = tag.getTextFrame("TLEN") real_tlen = f.info.time_secs * 1000 if tlen is None or int(tlen) != real_tlen: print("\tSetting TLEN (%d)" % real_tlen) tag.setTextFrame("TLEN", UnicodeType(real_tlen)) edited_files.add(f) # Add custom album type if special and otherwise not able to be # determined. curr_type = tag.album_type if curr_type != dir_type: print("\tSetting %s = %s" % (TXXX_ALBUM_TYPE, dir_type)) tag.album_type = dir_type edited_files.add(f) try: if not self._checkCoverArt(directory, audio_files): if not prompt("Proceed without valid cover file", default=True): return finally: self._dir_images = [] # Determine other changes, like file and/or directory renames # so they can be reported before save confirmation. # File renaming file_renames = [] if self.args.file_rename_pattern: format_str = self.args.file_rename_pattern else: if dir_type == SINGLE_TYPE: format_str = SINGLE_FNAME_FORMAT elif dir_type in (VARIOUS_TYPE, COMP_TYPE): format_str = VARIOUS_FNAME_FORMAT else: format_str = NORMAL_FNAME_FORMAT for f in audio_files: orig_name, orig_ext = os.path.splitext(os.path.basename(f.path)) new_name = TagTemplate(format_str).substitute(f.tag, zeropad=True) if orig_name != new_name: printMsg(u"Rename file to %s%s" % (new_name, orig_ext)) file_renames.append((f, new_name, orig_ext)) # Directory renaming dir_rename = None if dir_type != SINGLE_TYPE: if self.args.dir_rename_pattern: dir_format = self.args.dir_rename_pattern else: if dir_type == LIVE_TYPE: dir_format = LIVE_DNAME_FORMAT else: dir_format = NORMAL_DNAME_FORMAT template = TagTemplate(dir_format, dotted_dates=self.args.dotted_dates) pref_dir = template.substitute(audio_files[0].tag, zeropad=True) if os.path.basename(directory) != pref_dir: new_dir = os.path.join(os.path.dirname(directory), pref_dir) printMsg("Rename directory to %s" % new_dir) dir_rename = (directory, new_dir) # Cruft files to remove file_removes = [] if self._dir_files_to_remove: for f in self._dir_files_to_remove: print("Remove file: " + os.path.basename(f)) file_removes.append(f) self._dir_files_to_remove = set() if not self.args.dry_run: confirmed = False if (edited_files or file_renames or dir_rename or file_removes): confirmed = prompt("\nSave changes", default=True) if confirmed: for f in edited_files: print(u"Saving %s" % os.path.basename(f.path)) f.tag.save(version=ID3_V2_4, preserve_file_time=True) for f, new_name, orig_ext in file_renames: printMsg(u"Renaming file to %s%s" % (new_name, orig_ext)) f.rename(new_name, preserve_file_time=True) if file_removes: for f in file_removes: printMsg("Removing file %s" % os.path.basename(f)) os.remove(f) if dir_rename: printMsg("Renaming directory to %s" % dir_rename[1]) s = os.stat(dir_rename[0]) os.rename(dir_rename[0], dir_rename[1]) # With a rename use the origianl access time os.utime(dir_rename[1], (s.st_atime, s.st_atime)) else: printMsg("\nNo changes made (run without -n/--dry-run)")
def handleFile(self, f, *args, **kwargs): super().handleFile(f) if self.audio_file and self.audio_file.tag: xml = self.getXML(self.audio_file) printMsg(xml)
def handleFile(self, f): parse_version = self.args.tag_version super(ClassicPlugin, self).handleFile(f, tag_version=parse_version) if not self.audio_file: return self.printHeader(f) printMsg("-" * 79) new_tag = False if not self.audio_file.tag or self.handleRemoves(self.audio_file.tag): self.audio_file.initTag(version=parse_version) new_tag = True save_tag = ( self.handleEdits(self.audio_file.tag) or self.handlePadding(self.audio_file.tag) or self.args.force_update or self.args.convert_version ) self.printAudioInfo(self.audio_file.info) if not save_tag and new_tag: printError("No ID3 %s tag found!" % id3.versionToString(self.args.tag_version)) return self.printTag(self.audio_file.tag) if save_tag: # Use current tag version unless a convert was supplied version = self.args.convert_version or self.audio_file.tag.version printWarning("Writing ID3 version %s" % id3.versionToString(version)) # DEFAULT_MAX_PADDING is not set up as argument default, # because we don't want to rewrite the file if the user # did not trigger that explicitly: max_padding = self.args.max_padding if max_padding is True: max_padding = DEFAULT_MAX_PADDING self.audio_file.tag.save( version=version, encoding=self.args.text_encoding, backup=self.args.backup, preserve_file_time=self.args.preserve_file_time, max_padding=max_padding, ) if self.args.rename_pattern: # Handle file renaming. from eyed3.id3.tag import TagTemplate template = TagTemplate(self.args.rename_pattern) name = template.substitute(self.audio_file.tag, zeropad=True) orig = self.audio_file.path try: self.audio_file.rename(name) printWarning("Renamed '%s' to '%s'" % (orig, self.audio_file.path)) except IOError as ex: printError(ex.message) printMsg("-" * 79)