def checkAlbumsWithDifferentReleases(): c = MusicDatabase.getCursor() sql = text('SELECT album_id, path, ' ' COUNT(DISTINCT musicbrainz.release.id) ' ' FROM songs_mb, albums, album_songs, musicbrainz.release ' ' WHERE albums.id = album_songs.album_id ' ' AND releaseid = mbid ' ' AND songs_mb.song_id = album_songs.song_id ' ' GROUP BY album_songs.album_id, albums.path ' ' HAVING COUNT(DISTINCT musicbrainz.release.id) > 1') result = c.execute(sql) table = [(str(album_id), path, str(count)) for album_id, path, count in result.fetchall()] if table: table.insert(0, ('ALBUMID', 'PATH', 'NUMBER OF RELEASES')) aligned = alignColumns(table, (False, True, False)) print('Albums that contain songs from different releases:') for line in aligned: print(line) return bool(table)
def checkMusicBrainzTags(): c = MusicDatabase.getCursor() sql = text('SELECT id, path FROM songs ' 'WHERE root = :root ' ' AND NOT EXISTS (SELECT song_id ' ' FROM songs_mb ' ' WHERE recordingid is not NULL ' ' AND song_id = id)' ' ORDER BY id') table = [] for root in config['musicbrainzTaggedMusicPaths']: result = c.execute(sql, {'root': root}) table.extend((str(song_id), path) for song_id, path in result.fetchall()) if table: table.insert(0, ('SONGID', 'PATH')) aligned = alignColumns(table, (False, True)) print('Songs which should have musicbrainz tags but don\'t:') for line in aligned: print(line) return bool(table)
def checkAlbumsWithDifferentFormats(): c = MusicDatabase.getCursor() sql = text('select id, path, format ' ' from albums, album_properties ' ' where id in (select album_id ' ' from (select album_id, count(*) ' ' from album_properties ' ' group by album_id ' ' having count(*)>1) ' ' as foo) ' ' and id = album_id') result = c.execute(sql) table = [(str(album_id), path, audioFormat) for album_id, path, audioFormat in result.fetchall()] if table: table.insert(0, ('ALBUMID', 'PATH', 'FORMAT')) aligned = alignColumns(table, (False, True, True)) print('Albums that contain songs with different formats:') for line in aligned: print(line) return bool(table)
def performBackup(self): size_balance = SizeBalance() user_input = '' for dirpath, dirnames, filenames in os.walk(self.source, topdown=True): print(dirpath) filenames.sort() dirnames.sort() for pattern in reversed(self.priorityPatterns): for i in range(len(filenames)): if pattern in filenames[i]: filenames.insert(0, filenames.pop(i)) for i in range(len(dirnames)): if pattern in dirnames[i]: dirnames.insert(0, dirnames.pop(i)) tgt_dirpath = self.targetPath(dirpath) try: remoteDirCache = { os.path.join(tgt_dirpath, x.filename): x for x in self.sftp.listdir_attr(tgt_dirpath) } except FileNotFoundError: src_attr = os.stat(self.source) mkdir = CreateRemoteDir(self.source, tgt_dirpath, src_attr) mkdir.run(self.sftp) remoteDirCache = {} pending_changes = [] for filename in filenames: path = os.path.join(dirpath, filename) target_path = self.targetPath(path) shouldCopy, isDifferent, src_attr, tgt_attr = \ self.compareSrcAndTgt(path, target_path, remoteDirCache) if shouldCopy: upload = Upload(path, target_path, src_attr, tgt_attr) pending_changes.append(upload) for dirname in dirnames: path = os.path.join(dirpath, dirname) target_path = self.targetPath(path) try: attr = remoteDirCache[target_path] if not stat.S_ISDIR(attr.st_mode): print('Directory %s is a file in target %s (%s)' % (Color.Filename + path + Color.ENDC, Color.Host + self.target + Color.ENDC, Color.Filename + target_path + Color.ENDC)) sys.exit(1) except (KeyError, FileNotFoundError): src_attr = os.stat(path) upload = CreateRemoteDir(path, target_path, src_attr) pending_changes.append(upload) try: attr = self.sftp.stat(tgt_dirpath) iterate_tgt_dirpath = stat.S_ISDIR(attr.st_mode) except FileNotFoundError: iterate_tgt_dirpath = False if iterate_tgt_dirpath: for f_attr in remoteDirCache.values(): is_file = stat.S_ISREG(f_attr.st_mode) is_dir = stat.S_ISDIR(f_attr.st_mode) if ((is_file and f_attr.filename not in filenames) or (is_dir and f_attr.filename not in dirnames)): tgt_filename = os.path.join(tgt_dirpath, f_attr.filename) path = os.path.join(dirpath, f_attr.filename) remove = RemoveRemote(path, tgt_filename, tgt_attr=f_attr) pending_changes.append(remove) if any(c.requires_confirmation() for c in pending_changes): pending_changes.sort() if any(isinstance(c, RemoveRemote) for c in pending_changes): descs = [c.describe_change() for c in pending_changes] else: descs = [ c.describe_change() for c in pending_changes if c.requires_confirmation() ] descs = [x for x in chain(*descs)] aligned_descs = alignColumns(descs, (True, True, False, True)) txt = ('The following changes in %s require confirmation.\n' '%s\n' f'Submit changes to %s? \n' % (Color.Filename + dirpath + Color.ENDC, '\n'.join(aligned_descs), Color.Host + self.server + Color.ENDC + ':' + Color.Filename + tgt_dirpath + Color.ENDC)) if user_input not in ['a', 'l']: options = [ 'S', 'a', 'k', 'l', 'p', 'c', 'll', 'lr', 'llr', 'lrr' ] helpers = { 'c': partial(self.compareChangesInTasks, tasks=pending_changes), 'll': partial(self.listLocalDir, path=dirpath, pending_changes=pending_changes), 'lr': partial(self.listRemoteDir, path=tgt_dirpath, pending_changes=pending_changes), 'llr': partial(self.listLocalDir, path=dirpath, pending_changes=pending_changes, recursive=True), 'lrr': partial(self.listRemoteDir, path=tgt_dirpath, pending_changes=pending_changes, recursive=True) } options_long = [ '(s)ubmit', 'submit (a)lways', '(k)eep remote files', 'keep a(l)ways', 'ski(p)', '(c)ompare', '(ll) list local', '(lr) list remote', '(llr) list local recursive', '(lrr) list remote recursive' ] user_input = self.askUser(txt, options, options_long, helpers) else: print(txt + user_input) for change in pending_changes: if not change.requires_confirmation(): size_balance += change.run(self.sftp) continue if user_input == 'p': continue change.set_overwrite(user_input in ['s', 'a']) size_balance += change.run(self.sftp) else: # changes don't require confirmation for change in pending_changes: size_balance += change.run(self.sftp) print('---------------------------------------')
def print_change_description(descs, prefix='', end='\n'): if isinstance(descs, tuple): print(prefix + ' '.join(descs), end=end) return print(prefix + '\n'.join(alignColumns(descs, (True, True, False, True))), end=end)
def print_song_info(song, userID=None, print_analysis=True): # noqa: C901 song.loadMetadataInfo() print("----------") try: filesize = "%d bytes" % os.path.getsize(song.path()) except FileNotFoundError: filesize = "File not found" print("%s (%s)" % (song.path(), filesize)) print("song id:", song.id) rating = song.userRating(userID) if rating: print("my rating:", '*' * rating, '(%d/10)' % rating) avgrating = song.avgRating(userID) if avgrating: print("avg rating:", '*' * avgrating, '(%d/10)' % avgrating) if not rating and not avgrating: rating = song.rating(userID) print("rating:", '*' * rating, '(%d/10)' % rating) for k in sorted(song.metadata): v = song.metadata[k] print(TerminalColors.Header + str(k) + TerminalColors.ENDC + ' : ' + str(v)[:100]) if k in ('TMCL', 'TIPL'): if len(v) > 1: print('*** More than one %s tag in file: ' + 'The following list might be incomplete ***') txt_repr = v[0] m = re.search(r'people=', txt_repr) if m: pos_bracket = simple_find_matching_square_bracket( txt_repr, m.end()) txt = txt_repr[m.end():pos_bracket + 1] list_artists = eval(txt) for instrument, artist in list_artists: print(' %s : %s' % (instrument, artist)) print("file sha256sum: ", song.fileSha256sum()) print("audio track sha256sum: ", song.audioSha256sum()) print('duration: %s s' % formatLength(song.duration())) print(('duration without silences: %s s' % formatLength(song.durationWithoutSilences())), ' (silences: %s + %s)' % (formatLength(song.silenceAtStart()), formatLength(song.silenceAtEnd()))) printProperties(song) if song.coverWidth(): print('cover: %dx%d' % (song.coverWidth(), song.coverHeight())) if print_analysis: print_song_info_analysis(song) similar_pairs = MusicDatabase.getSimilarSongsToSongID(song.id) if similar_pairs: print('Similar songs:') similarSongs = [] for otherID, offset, similarity in similar_pairs: otherSong = getSongs(songID=otherID)[0] try: audioComparison = song.audioCmp(otherSong, interactive=False) except (DifferentLengthException): audioComparison = 2 colors = {'length': TerminalColors.Magenta} except (CantCompareSongsException): audioComparison = 3 colors = {} else: colors = {} colors['bitrate'] = TerminalColors.Blue propertiesString = getPropertiesAsString(otherSong, colors) color = {-1: TerminalColors.Worse, 0: TerminalColors.ENDC, 1: TerminalColors.Better, 2: TerminalColors.DifferentLength, 3: TerminalColors.CantCompareSongs}[audioComparison] songpath = (color + ' %d ' % otherID + otherSong.path() + TerminalColors.ENDC) songprop = '(%d %f %s)' % (offset, similarity, propertiesString) similarSongs.append([songpath] + songprop.split()) aligned = alignColumns(similarSongs) for line in aligned: print(line)