diff --git a/qobuz_dl/cli.py b/qobuz_dl/cli.py index 6df9404..c71b6c3 100644 --- a/qobuz_dl/cli.py +++ b/qobuz_dl/cli.py @@ -101,8 +101,20 @@ def main(): no_cover = config.getboolean("DEFAULT", "no_cover") no_database = config.getboolean("DEFAULT", "no_database") app_id = config["DEFAULT"]["app_id"] + + if ("folder_format" not in config["DEFAULT"] + or "track_format" not in config["DEFAULT"]): + logging.info(f'{YELLOW}Config file does not include format string,' + ' updating...') + config["DEFAULT"]["folder_format"] = "{artist} - {album} ({year}) " + "[{bit_depth}B-{sampling_rate}kHz]" + config["DEFAULT"]["track_format"] = "{tracknumber}. {tracktitle}" + with open(CONFIG_FILE, 'w') as cf: + config.write(cf) + folder_format = config["DEFAULT"]["folder_format"] track_format = config["DEFAULT"]["track_format"] + secrets = [ secret for secret in config["DEFAULT"]["secrets"].split(",") if secret ] @@ -137,9 +149,9 @@ def main(): no_cover=arguments.no_cover or no_cover, downloads_db=None if no_database or arguments.no_db else QOBUZ_DB, folder_format=arguments.folder_format - if hasattr(arguments, "folder_format") else folder_format, + if arguments.folder_format is not None else folder_format, track_format=arguments.track_format - if hasattr(arguments, "track_format") else track_format, + if arguments.track_format is not None else track_format, ) qobuz.initialize_client(email, password, app_id, secrets) diff --git a/qobuz_dl/commands.py b/qobuz_dl/commands.py index 22354a6..41731e8 100644 --- a/qobuz_dl/commands.py +++ b/qobuz_dl/commands.py @@ -108,7 +108,7 @@ def add_common_arg(custom_parser, default_folder, default_quality): metavar='PATTERN', help='pattern for formatting folder names, e.g ' '"{artist} - {album} ({year})". available keys: artist, ' - 'album, year, sampling_rate, bit_rate, tracktitle. ' + 'albumartist, album, year, sampling_rate, bit_rate, tracktitle. ' 'cannot contain characters used by the system, which includes /:<>', ) custom_parser.add_argument( diff --git a/qobuz_dl/downloader.py b/qobuz_dl/downloader.py index de3130c..11bba76 100644 --- a/qobuz_dl/downloader.py +++ b/qobuz_dl/downloader.py @@ -11,10 +11,17 @@ from qobuz_dl.color import OFF, GREEN, RED, YELLOW, CYAN from qobuz_dl.exceptions import NonStreamable QL_DOWNGRADE = "FormatRestrictedByFormatAvailability" -DEFAULT_MP3_FOLDER_FORMAT = '{artist} - {album} [MP3]' -DEFAULT_MP3_TRACK_FORMAT = '{tracknumber}. {tracktitle}' -DEFAULT_UNKNOWN_FOLDER_FORMAT = '{artist} - {album}' -DEFAULT_UNKNOWN_TRACK_FORMAT = '{tracknumber}. {tracktitle}' +# used in case of error +DEFAULT_FORMATS = { + 'MP3': [ + '{artist} - {album} ({year}) [MP3]', + '{tracknumber}. {tracktitle}', + ], + 'Unknown': [ + '{artist} - {album}', + '{tracknumber}. {tracktitle}', + ] +} logger = logging.getLogger(__name__) @@ -145,15 +152,16 @@ def download_and_tag( filename = os.path.join(root_dir, f".{tmp_count:02}.tmp") # Determine the filename - track_title = track_metadata["title"] - print(track_metadata) + track_title = track_metadata.get("title") + artist = _safe_get(track_metadata, "performer", "name") filename_attr = { - 'artist': track_metadata["performer"]["name"], - 'albumartist': track_metadata["album"]["artist"]["name"], + 'artist': artist, + 'albumartist': _safe_get(track_metadata, "album", "artist", "name", + default=artist), 'bit_depth': track_metadata['maximum_bit_depth'], 'sampling_rate': track_metadata['maximum_sampling_rate'], 'tracktitle': track_title, - 'version': track_metadata["version"], + 'version': track_metadata.get("version"), 'tracknumber': f"{track_metadata['track_number']:02}" } # track_format is a format string @@ -250,9 +258,9 @@ def download_id_by_type( 'bit_depth': bit_depth, 'sampling_rate': sampling_rate } - folder_format, track_format = clean_format_str(folder_format, - track_format, - file_format) + folder_format, track_format = _clean_format_str(folder_format, + track_format, + file_format) sanitized_title = sanitize_filename( folder_format.format(**album_attr) ) @@ -303,9 +311,9 @@ def download_id_by_type( is_track_id=True, track_url_dict=parse) file_format, quality_met, bit_depth, sampling_rate = format_info - folder_format, track_format = clean_format_str(folder_format, - track_format, - bit_depth) + folder_format, track_format = _clean_format_str(folder_format, + track_format, + bit_depth) if not downgrade_quality and not quality_met: logger.info( @@ -342,45 +350,46 @@ def download_id_by_type( # ----------- Utilities ----------- -def _clean_format_gen(s: str) -> str: - '''General clean for format strings. Avoids user errors. - ''' - if s.endswith('.mp3'): - s = s[:-4] - elif s.endswith('.flac'): - s = s[:-5] - s = s.strip() - return s - -def _not_mp3_valid(s: str) -> bool: - return 'bit_depth' in s or 'sample_rate' in s - - -def clean_format_str(folder: str, track: str, - file_format: str) -> Tuple[str, str]: - '''Cleans up the format strings to avoid errors +def _clean_format_str(folder: str, track: str, + file_format: str) -> Tuple[str, str]: + '''Cleans up the format strings, avoids errors with MP3 files. ''' - folder = _clean_format_gen(folder) - track = _clean_format_gen(track) - if file_format == 'MP3': - if _not_mp3_valid(folder): - logger.error(f'{RED}invalid format string for MP3: "{folder}"' - f'\ndefaulting to "{DEFAULT_MP3_FOLDER_FORMAT}"') - folder = DEFAULT_MP3_FOLDER_FORMAT - if _not_mp3_valid(track): - logger.error(f'{RED}invalid format string for MP3: "{track}"' - f'\ndefaulting to "{DEFAULT_MP3_TRACK_FORMAT}"') - track = DEFAULT_MP3_TRACK_FORMAT - elif file_format == 'Unknown': - if _not_mp3_valid(folder): - logger.error(f'{RED}Error getting format. Defaulting format ' - f'string to "{DEFAULT_UNKNOWN_FOLDER_FORMAT}"') - folder = DEFAULT_UNKNOWN_FOLDER_FORMAT - if _not_mp3_valid(track): - logger.error(f'{RED}Error getting format. Defaulting format ' - f'string to "{DEFAULT_UNKNOWN_TRACK_FORMAT}"') - track = DEFAULT_UNKNOWN_TRACK_FORMAT + final = [] + for i, fs in enumerate((folder, track)): + if fs.endswith('.mp3'): + fs = fs[:-4] + elif fs.endswith('.flac'): + fs = fs[:-5] + fs = fs.strip() - return (folder, track) + # default to pre-chosen string if format is invalid + if (file_format in ('MP3', 'Unknown') and + 'bit_depth' in file_format or 'sampling_rate' in file_format): + default = DEFAULT_FORMATS[file_format][i] + logger.error(f'{RED}invalid format string for format {file_format}' + f'. defaulting to {default}') + fs = default + final.append(fs) + + return tuple(final) + + +def _safe_get(d: dict, *keys, default=None): + '''A replacement for chained `get()` statements on dicts: + >>> d = {'foo': {'bar': 'baz'}} + >>> _safe_get(d, 'baz') + None + >>> _safe_get(d, 'foo', 'bar') + 'baz' + ''' + curr = d + res = default + for key in keys: + res = curr.get(key, default) + if res == default or not hasattr(res, '__getitem__'): + return res + else: + curr = res + return res