def _run(self): logo = figlet_format("``MishMash``", font="graffiti") print(Fg.green(logo, Style.BRIGHT)) self._displayMetaInfo() all_libs = { l.name: l for l in self.db_session.query(Library).filter( Library.id > NULL_LIB_ID).all() } lib_args = set(self.args.libs or all_libs.keys()) for lib in lib_args: if lib not in all_libs.keys(): print(Fg.red(f"Unknown library: {lib}")) continue lib = all_libs[lib] if self.args.show_artists: print(Fg.green(f"\n=== {lib.name} library artists ===")) self._displayArtists(lib) else: print(Fg.green(f"\n=== {lib.name} library ===")) self._displayLibraryInfo(lib)
def syncImage(img, current, session): """Add or updated the Image.""" def _img_str(i): return "%s - %s" % (i.type, i.description) for db_img in current.images: img_info = (img.type, img.md5, img.size) db_img_info = (db_img.type, db_img.md5, db_img.size) if db_img_info == img_info: img = None break elif (db_img.type == img.type and db_img.description == img.description): if img.md5 != db_img.md5: # Update image current.images.remove(db_img) current.images.append(img) session.add(current) pout(Fg.green("Updating image") + ": " + _img_str(img)) img = None break if img: # Add image current.images.append(img) session.add(current) pout(Fg.green("Adding image") + ": " + _img_str(img))
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 _run(self): session = self.db_session _output = [] def _addOutput(k, v): _output.append(tuple((k, v))) def _printOutput(_format, _olist): k_width = max([len(k) for k, v in _olist if k]) for k, v in _olist: if k: print((_format.format(k=k.ljust(k_width), v=v))) _olist.clear() logo = figlet_format("``MishMash``", font="graffiti") print((Fg.green(logo, Style.BRIGHT))) def mkkey(k): return Style.bright(Fg.blue(str(k))) def mkval(v): return Style.bright(Fg.blue(str(v))) _addOutput(mkkey("Version"), mkval(version)) _addOutput(mkkey("Database URL"), mkval(self.config.db_url)) try: meta = session.query(Meta).one() except (ProgrammingError, OperationalError) as ex: printError("\nError querying metadata. Database may not be " "initialized: %s" % str(ex)) return 1 _addOutput(mkkey("Database version"), mkval(meta.version)) _addOutput(mkkey("Last sync"), mkval(meta.last_sync or "Never")) _addOutput(mkkey("Configuration file "), mkval(self.args.config.filename or "None")) _printOutput("{k} : {v}", _output) def mkkey(k): return Style.bright(str(k)) print("") for lib in session.query(Library)\ .filter(Library.id > NULL_LIB_ID).all(): print((Fg.yellow("\n=== {} library ===").format(lib.name))) _addOutput(None, None) for name, orm_type in [ ("tracks", Track), ("artists", Artist), ("albums", Album), ("tags", Tag), ]: count = session.query(orm_type).filter_by(lib_id=lib.id)\ .count() _addOutput(mkkey(count), name) _printOutput("{k} music {v}", _output)
def _displayArtistMusic(self, artist, albums, singles): if albums: print("%d albums by %s:" % (len(albums), Style.bright(Fg.blue(artist.name)))) for alb in albums: print("%s %s" % (str(alb.getBestDate()).center(17), alb.title)) if singles: print("%d single tracks by %s" % (len(singles), Style.bright(Fg.blue(artist.name)))) for s in singles: print("\t%s" % (s.title))
def _run(self): logo = figlet_format("``MishMash``", font="graffiti") print(Fg.green(logo, Style.BRIGHT)) self._displayMetaInfo() for lib in Library.iterall(self.db_session, names=self.args.libs): if self.args.show_artists: print(Fg.green(f"\n=== {lib.name} library artists ===")) self._displayArtists(lib) else: print(Fg.green(f"\n=== {lib.name} library ===")) self._displayLibraryInfo(lib)
def _displayArtistMusic(self, artist, albums, singles): if albums: print(u"%d albums by %s:" % (len(albums), Style.bright(Fg.blue(artist.name)))) for alb in albums: print(u"%s %s" % (str(alb.getBestDate()).center(17), alb.title)) if singles: print(u"%d single tracks by %s" % (len(singles), Style.bright(Fg.blue(artist.name)))) for s in singles: print(u"\t%s" % (s.title))
def _write_log(self, environ, method, req_uri, start, status, bytes): if bytes is None: bytes = '-' if time.daylight: offset = time.altzone / 60 / 60 * -100 else: offset = time.timezone / 60 / 60 * -100 if offset >= 0: offset = "+%0.4d" % (offset) elif offset < 0: offset = "%0.4d" % (offset) remote_addr = '-' if environ.get('HTTP_X_FORWARDED_FOR'): remote_addr = environ['HTTP_X_FORWARDED_FOR'] elif environ.get('REMOTE_ADDR'): remote_addr = environ['REMOTE_ADDR'] stat = status.split(None, 1)[0] if (environ.get("webob._parsed_query_vars") and environ.get("webob._parsed_query_vars")[0].get("u")): user = environ.get("webob._parsed_query_vars")[0].get("u") user = environ.get('REMOTE_USER') or user or None else: user = None if user: remote_addr = "%s@%s" % (user, remote_addr) d = { 'REMOTE_ADDR': remote_addr, 'REQUEST_METHOD': method, 'REQUEST_URI': req_uri, 'HTTP_VERSION': environ.get('SERVER_PROTOCOL'), 'time': time.strftime('%d/%b/%Y:%H:%M:%S ', start) + offset, 'status': stat, 'bytes': bytes, 'HTTP_REFERER': environ.get('HTTP_REFERER', '-'), 'HTTP_USER_AGENT': environ.get('HTTP_USER_AGENT', '-'), } message = self.format % d stat = int(stat) if stat >= 200 and stat < 300: message = Fg.green(message) if stat >= 400 and stat < 500: message = Fg.yellow(message) if stat >= 500: message = Fg.red(message) self.logger.log(self.logging_level, message)
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 test_ansi_formats(): ansi_init(True) s = "Heavy Cream - Run Free" assert Fg.green(s, Style.BRIGHT, Style.UNDERLINE, Style.ITALICS) == \ Style.BRIGHT + Style.UNDERLINE + Style.ITALICS + Fg.GREEN + s + \ Fg.RESET + Style.RESET_ALL print("%(BLUE)sNice%(RESET)s" % Fg)
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 deleteOrphans(session): num_orphaned_artists = 0 num_orphaned_albums = 0 num_orphaned_tracks = 0 found_ids = set() # Tracks for track in session.query(Track).all(): if not os.path.exists(track.path): pout(Fg.red("Removing track") + ": " + track.path) session.delete(track) num_orphaned_tracks += 1 log.warn("Deleting track: %s" % str(track)) session.flush() # Artists found_ids.clear() for artist in session.query(Artist).all(): if (artist.id == VARIOUS_ARTISTS_ID or artist.id in found_ids): continue any_track = session.query(Track).filter(Track.artist_id == artist.id) \ .first() any_album = session.query(Album).filter(Album.artist_id == artist.id) \ .first() if not any_track and not any_album: log.warn("Deleting artist: %s" % str(artist)) session.delete(artist) num_orphaned_artists += 1 else: found_ids.add(artist.id) session.flush() # Albums found_ids.clear() for album in session.query(Album).all(): if album.id in found_ids: continue any_track = session.query(Track).filter(Track.album_id == album.id) \ .first() if not any_track: log.warn("Deleting album: %s" % str(album)) session.delete(album) num_orphaned_albums += 1 else: found_ids.add(album.id) return (num_orphaned_tracks, num_orphaned_artists, num_orphaned_albums)
def deleteOrphans(session): num_orphaned_artists = 0 num_orphaned_albums = 0 num_orphaned_tracks = 0 found_ids = set() # Tracks for track in session.query(Track).all(): if not os.path.exists(track.path): pout(Fg.red("Removing track") + ": " + track.path) session.delete(track) num_orphaned_tracks += 1 log.warn("Deleting track: %s" % str(track)) session.flush() # Albums found_ids.clear() for album in session.query(Album).all(): if album.id in found_ids: continue any_track = session.query(Track).filter(Track.album_id == album.id).first() if not any_track: log.warn("Deleting album: %s" % str(album)) session.delete(album) num_orphaned_albums += 1 else: found_ids.add(album.id) session.flush() # Artists found_ids.clear() for artist in session.query(Artist).all(): if (artist.id == VARIOUS_ARTISTS_ID or artist.id in found_ids): continue any_track = session.query(Track).filter(Track.artist_id == artist.id) \ .first() any_album = session.query(Album).filter(Album.artist_id == artist.id) \ .first() if not any_track and (not any_album or not any_album.tracks): log.warn("Deleting artist: %s" % str(artist)) session.delete(artist) num_orphaned_artists += 1 else: found_ids.add(artist.id) session.flush() return (num_orphaned_tracks, num_orphaned_artists, num_orphaned_albums)
def _getArtist(self, session, name, resolved_artist): artist_rows = session.query(Artist).filter_by( name=name, lib_id=self._lib.id).all() if artist_rows: if len(artist_rows) > 1 and resolved_artist: # Use previously resolved artist for this directory. artist = resolved_artist elif len(artist_rows) > 1: # Resolve artist try: heading = "Multiple artists names '%s'" % \ artist_rows[0].name artist = console.selectArtist(Fg.blue(heading), choices=artist_rows, allow_create=True) except PromptExit: log.warn("Duplicate artist requires user " "intervention to resolve.") artist = None else: if artist not in artist_rows: session.add(artist) session.flush() pout(Fg.blue("Updating artist") + ": " + name) resolved_artist = artist else: # Artist match artist = artist_rows[0] else: # New artist artist = Artist(name=name, lib_id=self._lib.id) session.add(artist) session.flush() pout(Fg.green("Adding artist") + ": " + name) return artist, resolved_artist
def handleDirectory(self, d, _): pout(Fg.blue("Syncing directory") + ": " + str(d)) audio_files = list(self._file_cache) self._file_cache = [] image_files = self._dir_images self._dir_images = [] if not audio_files: return d_datetime = datetime.fromtimestamp(getctime(d)) album_type = self._albumTypeHint(audio_files) or LP_TYPE album = None session = self._db_session for audio_file in audio_files: try: album = self._syncAudioFile(audio_file, album_type, d_datetime, session) except Exception as ex: # TODO: log and skip???? raise if album: # Directory images. for img_file in image_files: img_type = art.matchArtFile(img_file) if img_type is None: log.warn("Skipping unrecognized image file: %s" % img_file) continue new_img = Image.fromFile(img_file, img_type) if new_img: new_img.description = os.path.basename(img_file) syncImage( new_img, album if img_type in IMAGE_TYPES["album"] else album.artist, session) else: log.warn("Invalid image file: " + img_file) session.commit() if self.args.monitor: self._watchDir(d)
def makeResp(self, attrs=None, child=None, status=True, body=None): attrs = attrs or {} resp = self.req.response if isinstance(status, int) and not isinstance(status, bool): resp.status = status status = Command.E_GENERIC if body is None: body = self.makeBody(attrs, child, status) elif isinstance(body, ET.Element): body = "%s%s" % (XML_HEADER, ET.tostring(body).decode("utf-8")) pretty = None if "f" in self.req.params: if self.req.params["f"] == "jsonp" and "callback" in self.req.params: dct = self.toDict(body) if log.isEnabledFor(logging.DEBUG): pretty = "%s(%s)" % (self.req.params["callback"], json.dumps(dct, indent=3)) else: pretty = body txt = "%s(%s)" % (self.req.params["callback"], json.dumps(dct)) resp.text = txt resp.content_type = "application/javascript" elif self.req.params["f"] == "json": dct = self.toDict(body) if log.isEnabledFor(logging.DEBUG): pretty = json.dumps(dct, indent=3) else: pretty = body resp.text = json.dumps(dct) resp.content_type = "application/json" if not pretty: pretty = body resp.text = body resp.content_type = "text/xml" resp.charset = "UTF-8" log.info("Response(%s): %s" % (self.name, Fg.blue(pretty))) return resp
def _run(self): session = self.db_session lib = session.query(Library).filter(Library.name == self.args.lib).one() merge_list = [] for artist_arg in self.args.artists: artists = session.query(Artist)\ .filter(Artist.name == artist_arg)\ .filter(Artist.lib_id == lib.id).all() if len(artists) == 1: merge_list.append(artists[0]) elif len(artists) > 1: merge_list += selectArtist( Fg.blue("Select the artists to merge..."), multiselect=True, choices=artists) if len(merge_list) > 1: # Reuse lowest id artist_ids = {a.id: a for a in merge_list} min_id = min(*artist_ids.keys()) artist = artist_ids[min_id] mc = mostCommonItem new_artist = promptArtist( "Merging %d artists into new artist..." % len(merge_list), default_name=mc([a.name for a in merge_list]), default_city=mc([a.origin_city for a in merge_list]), default_state=mc([a.origin_state for a in merge_list]), default_country=mc([a.origin_country for a in merge_list]), artist=artist) new_artist.lib_id = lib.id else: print("Nothing to do, %s" % ("artist not found" if not len(merge_list) else "only one artist found")) return 1 assert(new_artist in merge_list) for artist in merge_list: if artist is new_artist: continue with session.no_autoflush: for alb in list(artist.albums): if alb.type != VARIOUS_TYPE: alb.artist_id = new_artist.id artist.albums.remove(alb) with session.no_autoflush: new_artist.albums.append(alb) for track in alb.tracks: if track.artist_id == artist.id: # gotta check in case alb is type various track.artist_id = new_artist.id for track in artist.getTrackSingles(): track.artist_id = new_artist.id # flush to get new artist ids in sync before delete, otherwise # cascade happens. session.flush() session.delete(artist) session.flush()
def mkkey(k): return Style.bright(Fg.blue(str(k)))
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 _syncAudioFile(self, audio_file, album_type, d_datetime, session): path = audio_file.path info = audio_file.info tag = audio_file.tag album = None is_various = (album_type == VARIOUS_TYPE) if not info or not tag: log.warn("File missing %s, skipping: %s" % ("audio" if not info else "tag/metadata", path)) return elif None in (tag.title, tag.artist): log.warn("File missing required artist and/or title " "metadata, skipping: %s" % path) return # Used when a duplicate artist is resolved for the entire directory. resolved_artist = None resolved_album_artist = None try: track = session.query(Track)\ .filter_by(path=path, lib_id=self._lib.id).one() except NoResultFound: track = None else: if datetime.fromtimestamp(getctime(path)) == track.ctime: # Track is in DB and the file is not modified. # stash the album though, we'll look for artwork # updates later album = track.album return # Either adding the track (track == None) # or modifying (track != None) artist, resolved_artist = self._getArtist(session, tag.artist, resolved_artist) if tag.album_type != SINGLE_TYPE: if tag.album_artist and tag.artist != tag.album_artist: album_artist, resolved_album_artist = \ self._getArtist(session, tag.album_artist, resolved_album_artist) else: album_artist = artist if artist is None: # see PromptExit return album_artist_id = album_artist.id if not is_various \ else VARIOUS_ARTISTS_ID album_rows = session.query(Album)\ .filter_by(title=tag.album, lib_id=self._lib.id, artist_id=album_artist_id).all() rel_date = tag.release_date rec_date = tag.recording_date or_date = tag.original_release_date if album_rows: if len(album_rows) > 1: # This artist has more than one album with the same # title. raise NotImplementedError("FIXME") album = album_rows[0] album.type = album_type album.release_date = rel_date album.original_release_date = or_date album.recording_date = rec_date pout(Fg.yellow("Updating album") + ": " + album.title) elif tag.album: album = Album(title=tag.album, lib_id=self._lib.id, artist_id=album_artist_id, type=album_type, release_date=rel_date, original_release_date=or_date, recording_date=rec_date, date_added=d_datetime) session.add(album) pout(Fg.green("Adding album") + ": " + album.title) session.flush() if not track: track = Track(audio_file=audio_file, lib_id=self._lib.id) self._num_added += 1 pout(Fg.green("Adding track") + ": " + path) else: track.update(audio_file) self._num_modified += 1 pout(Fg.yellow("Updating track") + ": " + path) genre = tag.genre genre_tag = None if genre: try: genre_tag = session.query(Tag)\ .filter_by(name=genre.name, lib_id=self._lib.id).one() except NoResultFound: genre_tag = Tag(name=genre.name, lib_id=self._lib.id) session.add(genre_tag) session.flush() track.artist_id = artist.id track.album_id = album.id if album else None if genre_tag: track.tags.append(genre_tag) session.add(track) if album: # Tag images for img in tag.images: for img_type in art.TO_ID3_ART_TYPES: if img.picture_type in \ art.TO_ID3_ART_TYPES[img_type]: break img_type = None if img_type is None: log.warn("Skipping unsupported image type: %s" % img.picture_type) continue new_img = Image.fromTagFrame(img, img_type) if new_img: syncImage( new_img, album if img_type in IMAGE_TYPES["album"] else album.artist, session) else: log.warn("Invalid image in tag") return album
def _run(self): session = self.db_session merge_list = [] for artist_arg in self.args.artists: artists = session.query(Artist)\ .filter(Artist.name == artist_arg).all() if len(artists) == 1: merge_list.append(artists[0]) elif len(artists) > 1: merge_list += selectArtist( Fg.blue("Select the artists to merge..."), multiselect=True, choices=artists) if len(merge_list) > 1: # Reuse lowest id artist_ids = {a.id: a for a in merge_list} min_id = min(*artist_ids.keys()) artist = artist_ids[min_id] mc = mostCommonItem new_artist = promptArtist( "Merging %d artists into new artist..." % len(merge_list), default_name=mc([a.name for a in merge_list]), default_city=mc([a.origin_city for a in merge_list]), default_state=mc([a.origin_state for a in merge_list]), default_country=mc([a.origin_country for a in merge_list]), artist=artist) else: print("Nothing to do, %s" % ("artist not found" if not len(merge_list) else "only one artist found")) return 1 assert (new_artist in merge_list) for artist in merge_list: if artist is new_artist: continue with session.no_autoflush: for alb in list(artist.albums): # FIXME: use constant if alb.type != "various": alb.artist_id = new_artist.id artist.albums.remove(alb) with session.no_autoflush: new_artist.albums.append(alb) for track in alb.tracks: if track.artist_id == artist.id: # gotta check in case alb is type various track.artist_id = new_artist.id for track in artist.getTrackSingles(): track.artist_id = new_artist.id # flush to get new artist ids in sync before delete, otherwise # cascade happens. session.flush() session.delete(artist) session.flush()
def test_ansi(): CSI = "\033[" class KnownFg: (GREY, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE) = [*range(30, 38)] RESET = 39 class KnownBg: (GREY, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE) = [*range(40, 48)] RESET = 49 class KnownStyle: (RESET_ALL, BRIGHT, DIM, ITALICS, UNDERLINE, BLINK_SLOW, BLINK_FAST, INVERSE) = [*range(0, 8)] STRIKE_THRU = 9 (RESET_BRIGHT, RESET_ITALICS, RESET_UNDERLINE, RESET_BLINK_SLOW, RESET_BLINK_FAST, RESET_INVERSE) = [*range(22, 28)] RESET_STRIKE_THRU = 29 RESET_DIM = RESET_BRIGHT def mkcode(c): return "{}{}m".format(CSI, c) ansi_init(True) for known_palette, palette in ( (KnownFg, Fg), (KnownBg, Bg), (KnownStyle, Style), ): code_list = [c for c in dir(known_palette) if c == c.upper()] # Test values and members if known_palette in (KnownFg, KnownBg): assert len(code_list) == 9 else: assert len(code_list) == 17 for c in code_list: assert type(getattr(palette, c)) is str assert mkcode(getattr(known_palette, c)) == \ getattr(palette, c) if palette is Style and c.lower().startswith("reset_"): # Style.reset_*() functions don't exist continue assert isinstance(getattr(palette, c.lower()), types.FunctionType) # Test palette functions vs codes assert Fg.BLUE + "SNFU" + Fg.RESET == Fg.blue("SNFU") code = getattr(palette, c) if palette is Style: reset = getattr(palette, "RESET_{}".format(c)) else: reset = getattr(palette, "RESET") func = getattr(palette, c.lower()) assert code + "SNFU" + reset == func("SNFU")
def test_NicfitApp_default(capfd): with pytest.raises(SystemExit): app.run([]) out, _ = capfd.readouterr() assert out == Fg.red("\m/ {} \m/".format(Style.inverse("Welcome"))) + "\n"
def mkval(v): return Style.bright(Fg.blue(str(v)))
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()