def _getArtist(self, audio_files): tags = [f.tag for f in audio_files if f.tag] artists = set([t.artist for t in tags if t.artist]) artist_name = None if len(artists) > 1: if self.args.dir_type != VARIOUS_TYPE: if prompt("Multiple artist names exist, process directory as " "various artists", default=True): self.args.dir_type = VARIOUS_TYPE artist_name = None else: artist_name = self._getOne("artist", artists) elif len(artists) == 0: if self.args.dir_type != VARIOUS_TYPE: # Various will be prompted as each file is walked since there # is no single value. artist_name = self._getOne("artist", []) else: if self.args.dir_type == VARIOUS_TYPE: if not prompt("--type is '%s' but the artists do not vary, " "continue?" % VARIOUS_TYPE, default=False): sys.exit(0) artist_name = artists.pop() assert artist_name or self.args.dir_type == VARIOUS_TYPE return artist_name if (not artist_name or not self.args.fix_case) else _fixCase(artist_name)
def promptArtist(text, name=None, default_name=None, default_city=None, default_state=None, default_country=None, artist=None): if text: print(text) if name is None: name = prompt(Fg.green("Artist name"), default=default_name) origin = {} for o in ("city", "state", "country"): origin["origin_%s" % o] = prompt(" %s" % Fg.green(o.title()), default=locals()["default_%s" % o], required=False) if not artist: artist = Artist(name=name, **origin) else: artist.name = name for o in origin: setattr(artist, o, origin[o]) return artist
def selectArtist(heading, choices=None, multiselect=False, allow_create=True): color = Fg.green artist = None name = None if heading: print(heading) while artist is None: if choices: name = choices[0].name for menu_num, a in enumerate(choices): print((" %d) %s" % (menu_num + 1, a.origin()))) menu_num += 1 if not multiselect: if allow_create: menu_num += 1 print((" %d) Enter a new artist" % menu_num)) choice = prompt("Which artist", type_=int, choices=list(range(1, menu_num + 1))) choice -= 1 if choice < len(choices): artist = choices[choice] # Otherwise fall through to select artist below else: def _validate(_resp): try: _ints = [ _i for _i in parseIntList(_resp) if _i in range(1, menu_num + 1) ] return bool(_ints) except: return False resp = prompt(color("Choose one or more artists"), validate=_validate) artists = [] for choice in [i - 1 for i in parseIntList(resp)]: artists.append(choices[choice]) # XXX: blech, returning a list here and a single value below return artists if artist is None: artist = promptArtist(None, name=name) if choices: if not Artist.checkUnique(choices + [artist]): print( (Fg.red("Artist entered is not unique, try again..."))) artist = None assert (artist) return artist
def selectArtist(heading, choices=None, multiselect=False, allow_create=True): color = Fg.green artist = None name = None menu_num = 0 if heading: print(heading) while artist is None: if choices: name = choices[0].name for menu_num, a in enumerate(choices, start=1): print(" %d) %s" % (menu_num + 1, a.origin())) if not multiselect: if allow_create: menu_num += 1 print(" %d) Enter a new artist" % menu_num) choice = prompt("Which artist", type_=int, choices=range(1, menu_num + 1)) choice -= 1 if choice < len(choices): artist = choices[choice] # Otherwise fall through to select artist below else: def _validate(_resp): try: _ints = [_i for _i in parseIntList(_resp) if _i in range(1, menu_num + 1)] return bool(_ints) except Exception: return False resp = prompt(color("Choose one or more artists"), validate=_validate) artists = [] for choice in [i - 1 for i in parseIntList(resp)]: artists.append(choices[choice]) # XXX: blech, returning a list here and a single value below return artists if artist is None: artist = promptArtist(None, name=name) if choices: if not Artist.checkUnique(choices + [artist]): print(Fg.red("Artist entered is not unique, try again...")) artist = None assert(artist) return artist
def _resolveArtistInfo(self, audio_files): assert (self._curr_dir_type != SINGLE_TYPE) tags = [f.tag for f in audio_files if f.tag] artists = set([t.album_artist for t in tags if t.album_artist]) # There can be 0 or 1 album artist values. album_artist = None if len(artists) > 1: album_artist = self._getOne("album artist", artists, required=False) elif artists: album_artist = artists.pop() artists = list(set([t.artist for t in tags if t.artist])) if len(artists) > 1: # There can be more then 1 artist when VARIOUS_TYPE or # album_artist != None. if not album_artist and self._curr_dir_type != VARIOUS_TYPE: if prompt( "Multiple artist names exist, process directory as " "various artists", default=True): self._curr_dir_type = VARIOUS_TYPE else: artists = [self._getOne("artist", artists, required=True)] elif (album_artist == VARIOUS_ARTISTS and self._curr_dir_type != VARIOUS_TYPE): self._curr_dir_type = VARIOUS_TYPE elif len(artists) == 0: artists = [self._getOne("artist", [], required=True)] # Fix up artist and album artist discrepancies if len(artists) == 1 and album_artist: artist = artists[0] if (album_artist != artist): print("When there is only one artist it should match the " "album artist. Choices are: ") for s in [artist, album_artist]: print("\t%s" % s) album_artist = prompt("Select common artist and album artist", choices=[artist, album_artist]) artists = [album_artist] if self.args.fix_case: album_artist = _fixCase(album_artist) artists = [_fixCase(a) for a in artists] return album_artist, artists
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 _resolveArtistInfo(self, audio_files): assert(self._curr_dir_type != SINGLE_TYPE) tags = [f.tag for f in audio_files if f.tag] artists = set([t.album_artist for t in tags if t.album_artist]) # There can be 0 or 1 album artist values. album_artist = None if len(artists) > 1: album_artist = self._getOne("album artist", artists, required=False) elif artists: album_artist = artists.pop() artists = list(set([t.artist for t in tags if t.artist])) if len(artists) > 1: # There can be more then 1 artist when VARIOUS_TYPE or # album_artist != None. if not album_artist and self._curr_dir_type != VARIOUS_TYPE: if prompt("Multiple artist names exist, process directory as " "various artists", default=True): self._curr_dir_type = VARIOUS_TYPE else: artists = [self._getOne("artist", artists, required=True)] elif (album_artist == VARIOUS_ARTISTS and self._curr_dir_type != VARIOUS_TYPE): self._curr_dir_type = VARIOUS_TYPE elif len(artists) == 0: artists = [self._getOne("artist", [], required=True)] # Fix up artist and album artist discrepancies if len(artists) == 1 and album_artist: artist = artists[0] if (album_artist != artist): print("When there is only one artist it should match the " "album artist. Choices are: ") for s in [artist, album_artist]: print("\t%s" % s) album_artist = prompt("Select common artist and album artist", choices=[artist, album_artist]) artists = [album_artist] if self.args.fix_case: album_artist = _fixCase(album_artist) artists = [_fixCase(a) for a in artists] return album_artist, artists
def _getOne(self, key, values, default=None, Type=unicode): values = set(values) if None in values: values.remove(None) if len(values) != 1: print( u"Detected %s %s names%s" % ( "0" if len(values) == 0 else "multiple", key, "." if not values else (": %s" % ", ".join([str(v) for v in values])), ) ) value = prompt(u"Enter %s" % key.capitalize(), default=default, type_=Type) else: value = values.pop() return value
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, 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 _run(self): session = self.db_session artists = session.query(Artist)\ .filter(Artist.name == self.args.artist).all() if not artists: print(u"Artist not found: %s" % self.args.artist) return 1 elif len(artists) > 1: artist = selectArtist(Fg.blue("Select which '%s' to split...") % artists[0].name, choices=artists, allow_create=False) else: artist = artists[0] # Albums by artist albums = list(artist.albums) + artist.getAlbumsByType(VARIOUS_TYPE) # Singles by artist and compilations the artist appears on singles = artist.getTrackSingles() if len(albums) < 2 and len(singles) < 2: print("%d albums and %d singles found for '%s', nothing to do." % (len(albums), len(singles), artist.name)) return 0 self._displayArtistMusic(artist, albums, singles) def _validN(_n): return _n > 1 and _n <= len(albums) n = prompt("\nEnter the number of distinct artists", type_=int, validate=_validN) new_artists = [] for i in range(1, n + 1): print(Style.bright(u"\n%s #%d") % (Fg.blue(artist.name), i)) # Reuse original artist for first a = artist if i == 1 else Artist(name=artist.name, date_added=artist.date_added) a.origin_city = prompt(" City", required=False) a.origin_state = prompt(" State", required=False) a.origin_country = prompt(" Country", required=False, type_=normalizeCountry) new_artists.append(a) if not Artist.checkUnique(new_artists): print(Fg.red("Artists must be unique.")) return 1 for a in new_artists: session.add(a) # New Artist objects need IDs session.flush() print(Style.bright("\nAssign albums to the correct artist.")) for i, a in enumerate(new_artists): print( "Enter %s%d%s for %s from %s%s%s" % (Style.BRIGHT, i + 1, Style.RESET_BRIGHT, a.name, Style.BRIGHT, a.origin(country_code="iso3c", title_case=False), Style.RESET_BRIGHT)) # prompt for correct artists def _promptForArtist(_text): a = prompt(_text, type_=int, choices=range(1, len(new_artists) + 1)) return new_artists[a - 1] print("") for alb in albums: # Get some of the path to help the decision path = commonDirectoryPrefix(*[t.path for t in alb.tracks]) path = os.path.join(*path.split(os.sep)[-2:]) a = _promptForArtist("%s (%s)" % (alb.title, path)) if alb.type != "various": alb.artist_id = a.id for track in alb.tracks: if track.artist_id == artist.id: track.artist_id = a.id print("") for track in singles: a = _promptForArtist(track.title) track.artist_id = a.id session.flush()
def _promptForArtist(_text): a = prompt(_text, type_=int, choices=range(1, len(new_artists) + 1)) return new_artists[a - 1]
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 # 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() 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.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 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.album_type 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.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, self.args.dir_type)) tag.album_type = self.args.dir_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, preserve_file_time=True) # FIXME Preserve file date on rename for f, new_name, orig_ext in file_renames: printMsg(u"Renaming file to %s%s" % (new_name, orig_ext)) f.rename(new_name) # FIXME Preserve directory date on rename 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 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 _run(self): session = self.db_session lib = session.query(Library).filter(Library.name == self.args.lib).one() artists = session.query(Artist).filter(Artist.lib_id == lib.id)\ .filter(Artist.name == self.args.artist)\ .all() if not artists: print("Artist not found: %s" % self.args.artist) return 1 elif len(artists) > 1: artist = selectArtist(Fg.blue("Select which '%s' to split...") % artists[0].name, choices=artists, allow_create=False) else: artist = artists[0] # Albums by artist albums = list(artist.albums) + artist.getAlbumsByType(VARIOUS_TYPE) # Singles by artist and compilations the artist appears on singles = artist.getTrackSingles() if len(albums) < 2 and len(singles) < 2: print("%d albums and %d singles found for '%s', nothing to do." % (len(albums), len(singles), artist.name)) return 0 self._displayArtistMusic(artist, albums, singles) def _validN(_n): try: return _n > 1 and _n <= len(albums) except Exception: return False n = prompt("\nEnter the number of distinct artists", type_=int, validate=_validN) new_artists = [] for i in range(1, n + 1): print(Style.bright("\n%s #%d") % (Fg.blue(artist.name), i)) # Reuse original artist for first a = artist if i == 1 else Artist(name=artist.name, date_added=artist.date_added, lib_id=artist.lib_id) a.origin_city = prompt(" City", required=False) a.origin_state = prompt(" State", required=False) a.origin_country = prompt(" Country", required=False, type_=normalizeCountry) new_artists.append(a) if not Artist.checkUnique(new_artists): print(Fg.red("Artists must be unique.")) return 1 for a in new_artists: session.add(a) # New Artist objects need IDs session.flush() print(Style.bright("\nAssign albums to the correct artist.")) for i, a in enumerate(new_artists): print("Enter %s%d%s for %s from %s%s%s" % (Style.BRIGHT, i + 1, Style.RESET_BRIGHT, a.name, Style.BRIGHT, a.origin(country_code="iso3c", title_case=False), Style.RESET_BRIGHT)) # prompt for correct artists def _promptForArtist(_text): a = prompt(_text, type_=int, choices=range(1, len(new_artists) + 1)) return new_artists[a - 1] print("") for alb in albums: # Get some of the path to help the decision path = commonDirectoryPrefix(*[t.path for t in alb.tracks]) path = os.path.join(*path.split(os.sep)[-2:]) a = _promptForArtist("%s (%s)" % (alb.title, path)) if alb.type != VARIOUS_TYPE: alb.artist_id = a.id for track in alb.tracks: if track.artist_id == artist.id: track.artist_id = a.id print("") for track in singles: a = _promptForArtist(track.title) track.artist_id = a.id session.flush()