def _get_tracks(self, path: Path, cue, interactive=True):
        result = []
        if cue is not None:
            tracks_data = cue.get_data_tracks()
            for data in tracks_data:
                if data['FILE'] is not None and 'TITLE' in data and 'INDEX' in data:
                    if Path(data['FILE']).name not in (p.name
                                                       for p in path.iterdir()
                                                       if p.is_file()):
                        ui.show(
                            f'WARNING: {data["FILE"]} declared in .cue not found'
                        )
                        continue
                    result.append(
                        Track(data['TITLE'], data['FILE'], data['INDEX']))
        if len(result) == 0:
            result = [
                Track(p.stem, p.name) for p in path.iterdir() if p.is_file()
                if p.suffix in self.supported_types
            ]

        tracks = '\n'.join(str(t) for t in result)
        if not interactive or ui.ask_ok(
                f"These tracks will be added from {path.absolute()}\n{tracks}\n"
        ):
            return result
    def _normalize_album_metadata(self, performer, year, title):
        metadata = [performer, year, title]
        indexes = ['ok', 'p', 'y', 't', 'ex']
        options = [
            'all right', 'change performer', 'change year', 'change title',
            'abort operation'
        ]

        while True:
            index, result = ui.choose(
                f"It's the correct album data?\n"
                f"Performer: {metadata[0]}\n"
                f"Year:      {metadata[1]}\n"
                f"Title:     {metadata[2]}\n", options, indexes)
            if index == 4:
                return None
            if index == 0:
                new_album = Album(*metadata, [])
                if metadata[0] in self._library and new_album in self._library[
                        metadata[0]]:
                    ui.show(
                        'Such an album already exists in the library\n'
                        'Try change metadata of new album or delete old one\n')
                    continue
                return metadata
            if index == 1:
                metadata[0] = self._get_performer_from_user()
                continue
            metadata[index - 1] = ui.get_input_from_user(
                f'Enter new {options[index].split()[1]}',
                read_int=True if index == 2 else False)
            if index == 3:
                metadata[2] = with_upper_first_letter(metadata[2])
    def _get_or_download_cover(self, performer, title, path):
        covers = [
            p for p in path.iterdir()
            if p.is_file() and p.suffix in self.covers_extensions
        ]
        if len(covers) > 0:
            return covers[0]
        lf = LastFM()

        if lf.try_download_cover(performer, title, path):
            ui.show('Successfully downloaded cover for album')
            return path.joinpath('cover.png')
Example #4
0
    def start(self):
        self._running = True
        while self._running:
            options = []
            indexes = []
            for k, v in available_commands.items():
                _, msg = v
                options.append(msg)
                indexes.append(k)

            idx, _ = ui.choose('Choose command', options, indexes)
            try:
                available_commands[indexes[idx]][0](self)
            except Exception as e:
                ui.show(str(e))
    def wrapper(*args, **kwargs):
        if len(args) < 2:
            raise ValueError

        file_name = args[1]
        if isinstance(file_name, Path):
            return f(*args, **kwargs)

        path = Path(file_name)
        path = path.resolve()
        if not path.exists():
            ui.show(f'Not found: {file_name}')
            if strict:
                raise FileNotFoundError
            return

        return f(*((args[0], path) + args[2:]), **kwargs)
Example #6
0
def main():
    args = parse_args()
    logging.basicConfig(format='%(message)s', level=logging.INFO)
    set_ui(ConsoleUI())

    if sys.platform == 'win32':
        asyncio.set_event_loop(asyncio.ProactorEventLoop())
    loop = asyncio.get_event_loop()

    lib_location = get_library_location(args)
    if lib_location == '' or not Path(lib_location).exists():
        ui.show('Not found library, specify it in settings.json or pass through arguments')
        sys.exit(1)

    with MusicLibrary(lib_location) as ml:
        ui.show(f'Library loaded [{lib_location}]')
        ml.show_library()
        with CommandsExecutor(ml, loop) as executor:
            executor.start()
    def add_album(self,
                  path: Path,
                  performer=None,
                  inside_ok=False,
                  delete_src=False,
                  interactive=True):
        if not path.is_dir():
            ui.show(f'Is not dir: {path.name}')
            return

        if not inside_ok and path in self._path.rglob(path.name):
            ui.show(f'Album already exists')
            return

        cue, cue_path = self._get_cue(path)
        tracks = self._get_tracks(path, cue, interactive)
        if tracks is None:
            ui.show('Tracks not found')
            return
        if performer is None:
            performer = self._get_performer(cue)
        year, title = self._get_year_and_title(path, cue)
        if interactive:
            normalized_metadata = self._normalize_album_metadata(
                performer, year, title)
            if normalized_metadata is None:
                return
            performer, year, title = normalized_metadata
        cover = self._get_or_download_cover(performer, title, path)
        cue_name = None if cue_path is None else cue_path.name
        cover_name = None if cover is None else cover.name
        album = Album(performer, year, title, tracks, cue_name, cover_name)
        try:
            if not album.get_location(self._path).exists():
                self._copy_album_to_library(album, path)
                if delete_src:
                    rmtree(path.absolute(), onerror=log_err)
        except OSError as e:
            ui.show(str(e))
            return
        self._library[performer].append(album)
        ui.show(f'Successfully added {year} - {title} to {performer}\n')
 def export(self, path):
     diff = list(self._get_diff(MusicLibrary(path)))
     if len(diff) == 0:
         ui.show('Library is up to date')
         return
     albums = {
         k: list(g)
         for k, g in groupby(diff, key=lambda e: e.performer)
     }
     if not ui.ask_ok(
             f'These albums will be copied from {path.absolute()}\n{self._get_albums_repr(albums)}'
     ):
         return
     for album in diff:
         src = album.get_location(self._path)
         dest = album.get_location(path)
         path.joinpath(album.performer).mkdir(parents=True, exist_ok=True)
         copytree(src.absolute(),
                  dest.absolute(),
                  copy_function=copy_with_progress)
     copy_with_progress(self._metadata, path)
     ui.show('Library exported')
Example #9
0
 def upload(self, folder):
     size = get_size(folder)
     if size > self._available_space:
         raise ValueError('Not enough space')
     ui.show('Zipping library')
     archive = make_archive('__library__', 'zip', root_dir=folder)
     ui.show('Successfully zipped')
     ui.show('Start uploading')
     self._disk.upload(archive, f'{self.root}/library.zip')
     remove(archive)
 def close(self):
     with self._metadata.open('wb') as f:
         dump(self._library, f)
     ui.show('Library saved')
def log_err(path, exc_info):
    _, e, _ = exc_info
    ui.show(f'{path}\n{str(e)}')
 def show_library(self):
     albums = self._get_albums_repr(self._library)
     ui.show(albums if albums != '\n' else 'Library is empty')
 def _clean_library(self):
     self._remove_non_exists_albums()
     self._update_albums_data()
     ui.show('Search for unknown albums in the library')
     self._handle_unknown_albums()
Example #14
0
 def close(self):
     if self._closed:
         return
     self._closed = True
     self._pb.print_progress_bar(100)
     ui.show('')
Example #15
0
async def download_album_async(album: Album, dest_folder):
    if not dest_folder.exists():
        raise FileNotFoundError(dest_folder)
    ui.show('Searching album description')
    tracks = album.get_tracks()
    year = album.get_wiki_published_date()
    if year is not None:
        year = year[-11:-7]
    else:
        year = ui.get_input_from_user('Enter album release date', read_int=True)
    performer = album.artist
    title = album.title
    dest = dest_folder.joinpath(f'{year} - {title}')
    dest.mkdir()
    ui.show('Downloading cover')
    LastFM().try_download_cover(performer, title, dest)

    ui.show('Searching tracks')
    video_urls = await gather(*[resolve_track_async(track.artist, track.title) for track in tracks])
    ui.show('Extracting sources')
    streams = [get_audio_download_stream(url) for url in video_urls]
    total_size = sum(s.filesize for s in streams if s is not None)
    tracker = ProgressTracker(total_size, 'Downloading album')
    destinations = []
    for i, stream in enumerate(streams):
        if stream is not None:
            destinations.append(dest.joinpath(f'{str(i+1).zfill(2)} {tracks[i].title}.{stream.subtype}'))
        else:
            ui.show(f'Not found {str(tracks[i])}')
            destinations.append(None)

    await gather(*[download_audio_async(stream, raw, on_chunk_read=tracker.add_progress)
                   for stream, raw in zip(streams, destinations)])
    tracker.close()
    ui.show('Converting to mp3')
    await gather(*[convert_to_audio_async(d, dest) for d in destinations if d is not None])
    for raw in filter(lambda e: e is not None, destinations):
        remove(raw)
    return dest
Example #16
0
 def main(self):
     ui.show(self.appName, self.config, self.project)
     self.config.write()