コード例 #1
0
def sane_file(ctx: click.Context, param: click.Parameter, value: Path) -> File:
    if not param.name:
        logger.error("no param name set")
        raise click.Abort()
    file = File.from_path(value)
    ctx.params[param.name] = file
    return file
コード例 #2
0
ファイル: folders.py プロジェクト: AdrienPensart/musicbot
 def worker(path: Path) -> File | None:
     try:
         file = File.from_path(path=path)
         return file.to_mp3(flat=flat, destination=destination)
     except MusicbotError as e:
         self.err(e)
     except Exception as e:  # pylint: disable=broad-except
         logger.error(f"{path} : unable to convert to mp3 : {e}")
     return None
コード例 #3
0
ファイル: youtube.py プロジェクト: AdrienPensart/musicbot
def find(file: File, acoustid_api_key: str) -> None:
    yt_path = f"{file.artist} - {file.title}.mp3"
    try:
        file_id = file.fingerprint(acoustid_api_key)
        print(
            f'Searching for artist {file.artist} and title {file.title} and duration {seconds_to_human(file.length)}'
        )
        ydl_opts = {
            'format':
            'bestaudio/best',
            'quiet':
            True,
            'cachedir':
            False,
            'no_warnings':
            True,
            'postprocessors': [{
                'key': 'FFmpegExtractAudio',
                'preferredcodec': 'mp3',
                'preferredquality': '192',
            }],
            'outtmpl':
            yt_path,
        }
        with youtube_dl.YoutubeDL(ydl_opts) as ydl:
            infos = ydl.extract_info(f"ytsearch1:'{file.artist} {file.title}'",
                                     download=True)
            url = None
            for entry in infos['entries']:
                url = entry['webpage_url']
                break

            yt_ids = acoustid.match(acoustid_api_key, yt_path)
            yt_id = None
            for _, recording_id, _, _ in yt_ids:
                yt_id = recording_id
                break
            if file_id == yt_id:
                print(f'Found: fingerprint {file_id} | url {url}')
            else:
                print(
                    f'Not exactly found: fingerprint file: {file_id} | yt: {yt_id} | url {url}'
                )
                print(f'Based only on duration, maybe: {url}')
    except acoustid.WebServiceError as e:
        logger.error(e)
    except youtube_dl.utils.DownloadError as e:
        logger.error(e)
    finally:
        try:
            if yt_path:
                os.remove(yt_path)
        except OSError:
            logger.warning(f"File not found: {yt_path}")
コード例 #4
0
def test_mp3_tags() -> None:
    m = File.from_path(path=fixtures.one_mp3)
    assert m.artist == "1995"
    assert m.title == "La Flemme"
    assert m.album == "La Source"
    assert m.genre == "Rap"
    assert m.track == 2
    assert m._comment == "rap french"
    assert m.keywords == {'rap', 'french'}
    assert m.rating == 4.5
    assert m.length == 258
コード例 #5
0
ファイル: music.py プロジェクト: AdrienPensart/musicbot
def tags(
    file: File,
    link_options: LinkOptions,
    output: str,
) -> None:
    logger.info(file.handle.tags.keys())
    music = file.to_music(link_options)
    if output == 'json':
        MusicbotObject.print_json(asdict(music))
        return
    print(music)
コード例 #6
0
def test_flac_tags() -> None:
    m = File.from_path(path=fixtures.one_flac)
    assert m.artist == "Buckethead"
    assert m.title == "Welcome To Bucketheadland"
    assert m.album == "Giant Robot"
    assert m.genre == "Avantgarde"
    assert m.track == 2
    assert m._description == "rock cutoff"
    assert m.keywords == {'rock', 'cutoff'}
    assert m.rating == 5.0
    assert m.length == 1
コード例 #7
0
ファイル: musicdb.py プロジェクト: AdrienPensart/musicbot
    async def upsert_path(
            self,
            path: Path,
            link_options: LinkOptions = DEFAULT_LINK_OPTIONS) -> File | None:
        try:
            file = File.from_path(path=path)

            if 'no-title' in file.inconsistencies or 'no-artist' in file.inconsistencies or 'no-album' in file.inconsistencies:
                MusicbotObject.warn(
                    f"{file} : missing mandatory fields title/album/artist : {file.inconsistencies}"
                )
                return None

            if self.dry:
                return file

            input_music = file.to_music(link_options)
            params = dict(
                query=UPSERT_QUERY,
                **asdict(input_music),
            )
            result = await self.client.query_required_single(**params)
            output_music = Music(
                title=result.name,
                artist=result.artist_name,
                album=result.album_name,
                genre=result.genre_name,
                size=result.size,
                length=result.length,
                keywords=set(result.all_keywords),
                track=result.track,
                rating=result.rating,
                links=set(result.links),
            )

            music_diff = DeepDiff(asdict(input_music),
                                  asdict(output_music),
                                  ignore_order=True)
            if music_diff:
                MusicbotObject.err(f"{file} : file and music diff detected : ")
                MusicbotObject.print_json(music_diff, file=sys.stderr)

            return file
        except edgedb.errors.TransactionSerializationError as e:
            raise MusicbotError(f"{path} : transaction error : {e}") from e
        except edgedb.errors.NoDataError as e:
            raise MusicbotError(
                f"{path} : no data result for query : {e}") from e
        except OSError as e:
            logger.error(e)
        return None
コード例 #8
0
ファイル: music.py プロジェクト: AdrienPensart/musicbot
def inconsistencies(
    file: File,
    fix: bool,
    checks: list[str],
) -> None:
    table = Table("Path", "Inconsistencies")
    try:
        if fix and not file.fix(checks=checks):
            MusicbotObject.err(f"{file} : unable to fix inconsistencies")

        if file.inconsistencies:
            table.add_row(str(file.path), ', '.join(file.inconsistencies))
    except (OSError, MutagenError):
        table.add_row(str(file.path), "could not open file")
    MusicbotObject.console.print(table)
コード例 #9
0
ファイル: music.py プロジェクト: AdrienPensart/musicbot
def set_tags(
    paths: list[Path],
    title: str | None = None,
    artist: str | None = None,
    album: str | None = None,
    genre: str | None = None,
    keywords: list[str] | None = None,
    rating: float | None = None,
    track: int | None = None,
) -> None:
    for path in paths:
        file = File.from_path(path=path)
        if not file.set_tags(
                title=title,
                artist=artist,
                album=album,
                genre=genre,
                keywords=keywords,
                rating=rating,
                track=track,
        ):
            MusicbotObject.err(f"{file} : unable to set tags")
コード例 #10
0
ファイル: folders.py プロジェクト: AdrienPensart/musicbot
 def worker(path: Path) -> File | None:
     try:
         return File.from_path(path=path)
     except OSError as e:
         logger.error(e)
     return None
コード例 #11
0
ファイル: local.py プロジェクト: AdrienPensart/musicbot
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}")
コード例 #12
0
ファイル: music.py プロジェクト: AdrienPensart/musicbot
def fingerprint(file: File, acoustid_api_key: str) -> None:
    print(file.fingerprint(acoustid_api_key))
コード例 #13
0
ファイル: music.py プロジェクト: AdrienPensart/musicbot
def flac2mp3(file: File, destination: Path) -> None:
    if not file.to_mp3(destination):
        MusicbotObject.err(f"{file} : unable to convert to MP3")
コード例 #14
0
ファイル: music.py プロジェクト: AdrienPensart/musicbot
def delete_keywords(file: File, keywords: set[str]) -> None:
    if not file.delete_keywords(keywords):
        MusicbotObject.err(f"{file} : unable to delete keywords")
コード例 #15
0
ファイル: music.py プロジェクト: AdrienPensart/musicbot
def add_keywords(file: File, keywords: set[str]) -> None:
    if not file.add_keywords(keywords):
        MusicbotObject.err(f"{file} : unable to add keywords")