Exemple #1
0
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)
Exemple #2
0
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
Exemple #3
0
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')
Exemple #4
0
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()
Exemple #5
0
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)}")
Exemple #6
0
def execute(musicdb: MusicDb, query: str) -> None:
    print(musicdb.sync_query(query))
Exemple #7
0
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}")
Exemple #8
0
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)
Exemple #9
0
def clean(musicdb: MusicDb) -> None:
    musicdb.sync_clean_musics()