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')
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)
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')
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()
def close(self): if self._closed: return self._closed = True self._pb.print_progress_bar(100) ui.show('')
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
def main(self): ui.show(self.appName, self.config, self.project) self.config.write()