def playlist( output: str, music_filter: MusicFilter, link_options: LinkOptions, musicdb: MusicDb, out: progressbar.utils.WrappingIO, ) -> None: musicdb.set_readonly() p = musicdb.sync_make_playlist( music_filter=music_filter, link_options=link_options, ) p.print(output=output, file=out)
def sane_musicdb(ctx: click.Context, param: click.Parameter, value: str) -> MusicDb: if param.name: ctx.params[param.name] = value dsn = ctx.params.pop('dsn') musicdb = MusicDb.from_dsn(dsn) ctx.params['musicdb'] = musicdb return musicdb
def player(music_filter: MusicFilter, musicdb: MusicDb, vlc_params: str) -> None: if not MusicbotObject.config.quiet: progressbar.streams.unwrap(stderr=True, stdout=True) try: future = musicdb.make_playlist(music_filter) playlist = async_run(future) playlist.play(vlc_params) except io.UnsupportedOperation: logger.critical('Unable to load UI')
def scan( musicdb: MusicDb, folders: Folders, clean: bool, save: bool, link_options: LinkOptions, output: str, coroutines: int, ) -> None: if clean: musicdb.sync_clean_musics() musics = musicdb.sync_upsert_folders( folders=folders, link_options=link_options, coroutines=coroutines, ) if output == 'json': MusicbotObject.print_json(musics) if save: MusicbotObject.config.configfile['musicbot'][ 'folders'] = folders.unique_directories MusicbotObject.config.write()
def diff(musicdb: MusicDb, download_playlist: bool, spotify: Spotify, output: str, min_threshold: float, max_threshold: float) -> None: spotify_tracks = spotify.liked_tracks() spotify_tracks_by_slug = { slugify(f"""{t['track']['artists'][0]['name']}-{t['track']['name']}""", stopwords=STOPWORDS, replacements=REPLACEMENTS): t for t in spotify_tracks } local = musicdb.sync_make_playlist() local_music_by_slug = {music.slug: music for music in local.musics} spotify_differences = set(spotify_tracks_by_slug.keys()).difference( set(local_music_by_slug.keys())) spotify_slug_tracks = dict( (d, spotify_tracks_by_slug[d]) for d in sorted(spotify_differences)) local_tracks_found = len(spotify_tracks_by_slug) - len(spotify_differences) if len(local.musics) == local_tracks_found: return if download_playlist: spotify.set_download_playlist(spotify_slug_tracks.values()) output_tracks(output, list(spotify_slug_tracks.values())) distances_tracks = [] for spotify_slug, spotify_track in spotify_slug_tracks.items(): distances = { local_slug: fuzz.ratio(spotify_slug, local_slug) for local_slug in local_music_by_slug } if not distances: continue closest_local_track = max(distances.items(), key=operator.itemgetter(1)) closest_local_slug = closest_local_track[0] closest_distance = closest_local_track[1] if min_threshold <= closest_distance <= max_threshold: if 'spotify-error' in local_music_by_slug[ closest_local_slug].keywords: continue distances_tracks.append({ 'local_track': local_music_by_slug[closest_local_slug], 'local_slug': closest_local_slug, 'spotify_track': spotify_track, 'spotify_slug': spotify_slug, 'distance': closest_distance, }) print_distances(distances_tracks) print(f"spotify tracks : {len(spotify_tracks)}") print(f"spotify slugs: {len(spotify_tracks_by_slug)}") print(f"local tracks : {len(local.musics)}") print(f"local tracks slugs : {len(local_music_by_slug)}") print(f"found in local : {local_tracks_found}") print(f"not found in local : {len(spotify_differences)}")
def execute(musicdb: MusicDb, query: str) -> None: print(musicdb.sync_query(query))
def sync( musicdb: MusicDb, music_filter: MusicFilter, delete: bool, destination: Path, yes: bool, flat: bool, ) -> None: logger.info(f'Destination: {destination}') future = musicdb.make_playlist(music_filter) playlist = async_run(future) if not playlist.musics: click.secho('no result for filter, nothing to sync') return folders = Folders(directories=[destination], extensions=set()) logger.info(f"Files : {len(folders.files)}") if not folders.files: logger.warning("no files found in destination") destinations = { str(path)[len(str(destination)) + 1:]: path for path in folders.paths } musics: list[File] = [] for music in playlist.musics: for link in music.links: try: if link.startswith('ssh://'): continue music_to_sync = File.from_path(Path(link)) musics.append(music_to_sync) except OSError as e: logger.error(e) logger.info(f"Destinations : {len(destinations)}") if flat: sources = {music.flat_filename: music.path for music in musics} else: sources = {music.filename: music.path for music in musics} logger.info(f"Sources : {len(sources)}") to_delete = set(destinations.keys()) - set(sources.keys()) if delete and (yes or click.confirm( f'Do you really want to delete {len(to_delete)} files and playlists ?' )): with MusicbotObject.progressbar(max_value=len(to_delete)) as pbar: for d in to_delete: try: path_to_delete = Path(destinations[d]) pbar.desc = f"Deleting musics and playlists: {path_to_delete.name}" if MusicbotObject.dry: logger.info(f"[DRY-RUN] Deleting {path_to_delete}") continue try: logger.info(f"Deleting {path_to_delete}") path_to_delete.unlink() except OSError as e: logger.error(e) finally: pbar.value += 1 pbar.update() to_copy = set(sources.keys()) - set(destinations.keys()) with MusicbotObject.progressbar(max_value=len(to_copy)) as pbar: logger.info(f"To copy: {len(to_copy)}") for c in sorted(to_copy): final_destination = destination / c try: path_to_copy = Path(sources[c]) pbar.desc = f'Copying {path_to_copy.name} to {destination}' if MusicbotObject.dry: logger.info( f"[DRY-RUN] Copying {path_to_copy.name} to {final_destination}" ) continue logger.info( f"Copying {path_to_copy.name} to {final_destination}") Path(final_destination).parent.mkdir(exist_ok=True) _ = shutil.copyfile(path_to_copy, final_destination) except KeyboardInterrupt: logger.debug(f"Cleanup {final_destination}") try: final_destination.unlink() except OSError: pass raise finally: pbar.value += 1 pbar.update() for d in folders.flush_empty_directories(): if any(e in d for e in folders.except_directories): logger.debug(f"Invalid path {d}") continue if not MusicbotObject.dry: shutil.rmtree(d) logger.info(f"[DRY-RUN] Removing empty dir {d}")
def bests( musicdb: MusicDb, music_filter: MusicFilter, link_options: LinkOptions, folder: Path, min_playlist_size: int, ratings: tuple[float, ...], types: list[str], ) -> None: musicdb.set_readonly() prefiltered = musicdb.sync_make_playlist(music_filter=music_filter, link_options=link_options) if "genre" in types and prefiltered.genres: with MusicbotObject.progressbar( max_value=len(prefiltered.genres), prefix="Generating bests genres") as pbar: async def genre_worker(genre: str) -> None: try: filter_copy = evolve( music_filter, genres=frozenset([genre]), ) best = await musicdb.make_playlist( music_filter=filter_copy, link_options=link_options) if len(best.musics) < min_playlist_size: return filepath = Path(folder) / ('genre_' + genre.lower() + '.m3u') best.write(filepath) finally: pbar.value += 1 pbar.update() _ = async_gather(genre_worker, prefiltered.genres) if "rating" in types and ratings: with MusicbotObject.progressbar( max_value=len(ratings), prefix="Generating bests ratings") as pbar: async def rating_worker(rating: float) -> None: try: filter_copy = evolve( music_filter, min_rating=rating, ) best = await musicdb.make_playlist( music_filter=filter_copy, link_options=link_options) if len(best.musics) < min_playlist_size: return filepath = Path(folder) / ('rating_' + str(rating) + '.m3u') best.write(filepath) finally: pbar.value += 1 pbar.update() _ = async_gather(rating_worker, ratings) if "keyword" in types and prefiltered.keywords: with MusicbotObject.progressbar( max_value=len(prefiltered.keywords), prefix="Generating bests keywords") as pbar: async def keyword_worker(keyword: str) -> None: try: filter_copy = evolve( music_filter, keywords=frozenset([keyword]), ) best = await musicdb.make_playlist( music_filter=filter_copy, link_options=link_options) if len(best.musics) < min_playlist_size: return filepath = Path(folder) / ('keyword_' + keyword.lower() + '.m3u') best.write(filepath) finally: pbar.value += 1 pbar.update() _ = async_gather(keyword_worker, prefiltered.keywords) if "artist" not in types: return with MusicbotObject.progressbar(max_value=len(prefiltered.artists), prefix="Generating bests artists") as pbar: async def artist_worker(artist: str) -> None: if "rating" in types and ratings: for rating in ratings: filter_copy = evolve( music_filter, min_rating=rating, artists=frozenset([artist]), ) best = await musicdb.make_playlist( music_filter=filter_copy, link_options=link_options) if len(best.musics) < min_playlist_size: continue filepath = Path(folder) / artist / ('rating_' + str(rating) + '.m3u') best.write(filepath) if "keyword" in types and prefiltered.keywords: for keyword in prefiltered.keywords: filter_copy = evolve( music_filter, keywords=frozenset([keyword]), artists=frozenset([artist]), ) best = await musicdb.make_playlist( music_filter=filter_copy, link_options=link_options) if len(best.musics) < min_playlist_size: continue filepath = Path(folder) / artist / ( 'keyword_' + keyword.lower() + '.m3u') best.write(filepath) _ = async_gather(artist_worker, prefiltered.artists)
def clean(musicdb: MusicDb) -> None: musicdb.sync_clean_musics()