More tweaks/fixes
This commit is contained in:
parent
911c29820a
commit
a10b32b5b7
|
@ -160,4 +160,5 @@ cython_debug/
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||||
|
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||||
|
|
||||||
|
// List of extensions which should be recommended for users of this workspace.
|
||||||
|
"recommendations": [
|
||||||
|
"matangover.mypy",
|
||||||
|
"ms-python.black-formatter",
|
||||||
|
"ms-python.flake8"
|
||||||
|
],
|
||||||
|
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,11 +1,7 @@
|
||||||
{
|
{
|
||||||
"python.linting.flake8Enabled": true,
|
"editor.defaultFormatter": "ms-python.black-formatter",
|
||||||
"python.linting.mypyEnabled": true,
|
|
||||||
"python.linting.enabled": true,
|
|
||||||
"python.formatting.provider": "black",
|
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.organizeImports": true
|
"source.organizeImports": true
|
||||||
},
|
},
|
||||||
"isort.args": ["--profile", "black"]
|
}
|
||||||
}
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## v1.0.0
|
## v1.0.0
|
||||||
|
|
||||||
An unexpected reboot
|
An unexpected reboot.
|
||||||
|
|
||||||
### BREAKING CHANGES AHEAD
|
### BREAKING CHANGES AHEAD
|
||||||
|
|
||||||
|
@ -29,9 +29,14 @@ An unexpected reboot
|
||||||
### Additions
|
### Additions
|
||||||
|
|
||||||
- Added new command line arguments
|
- Added new command line arguments
|
||||||
- `--library`/`-l` overrides both `music_library` and `podcast_library` options similar to `--output`
|
- `--library`/`-l` overrides both `music_library` and `podcast_library` options similar to `--output`/`-o`
|
||||||
- `--category`/`-c` will limit search results to a certain type, accepted values are "album", "artist", "playlist", "track", "show", "episode". Accepts multiple choices.
|
- `--category`/`-c` will limit search results to a certain type, accepted values are "album", "artist", "playlist", "track", "show", "episode". Accepts multiple choices.
|
||||||
- `--debug` shows full tracebacks on crash instead of just the final error message
|
- `--debug` shows full tracebacks on crash instead of just the final error message
|
||||||
|
- Added new shorthand aliases to some options:
|
||||||
|
- `-oa` = `--output-album`
|
||||||
|
- `-opt` = `--output-playlist-track`
|
||||||
|
- `-ope` = `--output-playlist-episode`
|
||||||
|
- `-op` = `--output-podcast`
|
||||||
- Search results can be narrowed down using search filters
|
- Search results can be narrowed down using search filters
|
||||||
- Available filters are 'album', 'artist', 'track', 'year', 'upc', 'tag:hipster', 'tag:new', 'isrc', and 'genre'.
|
- Available filters are 'album', 'artist', 'track', 'year', 'upc', 'tag:hipster', 'tag:new', 'isrc', and 'genre'.
|
||||||
- The 'artist' and 'year' filters only shows results from the given year or a range (e.g. 1970-1982).
|
- The 'artist' and 'year' filters only shows results from the given year or a range (e.g. 1970-1982).
|
||||||
|
|
17
setup.cfg
17
setup.cfg
|
@ -32,6 +32,19 @@ console_scripts =
|
||||||
zotify = zotify.__main__:main
|
zotify = zotify.__main__:main
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
# Conflicts with black
|
|
||||||
ignore = E203
|
|
||||||
max-line-length = 160
|
max-line-length = 160
|
||||||
|
|
||||||
|
[mypy]
|
||||||
|
warn_unused_configs = True
|
||||||
|
|
||||||
|
[mypy-librespot.*]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-music_tag]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-pwinput]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-tqdm]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
|
@ -5,15 +5,16 @@ from pathlib import Path
|
||||||
|
|
||||||
from zotify.app import App
|
from zotify.app import App
|
||||||
from zotify.config import CONFIG_PATHS, CONFIG_VALUES
|
from zotify.config import CONFIG_PATHS, CONFIG_VALUES
|
||||||
from zotify.utils import OptionalOrFalse
|
from zotify.utils import OptionalOrFalse, SimpleHelpFormatter
|
||||||
|
|
||||||
VERSION = "0.9.2"
|
VERSION = "0.9.3"
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = ArgumentParser(
|
parser = ArgumentParser(
|
||||||
prog="zotify",
|
prog="zotify",
|
||||||
description="A fast and customizable music and podcast downloader",
|
description="A fast and customizable music and podcast downloader",
|
||||||
|
formatter_class=SimpleHelpFormatter,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-v",
|
"-v",
|
||||||
|
@ -39,7 +40,7 @@ def main():
|
||||||
help="Specify a path to the root of a music/playlist/podcast library",
|
help="Specify a path to the root of a music/playlist/podcast library",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-o", "--output", type=str, help="Specify the output location/format"
|
"-o", "--output", type=str, help="Specify the output file structure/format"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-c",
|
"-c",
|
||||||
|
@ -101,7 +102,7 @@ def main():
|
||||||
for k, v in CONFIG_VALUES.items():
|
for k, v in CONFIG_VALUES.items():
|
||||||
if v["type"] == bool:
|
if v["type"] == bool:
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
v["arg"],
|
*v["args"],
|
||||||
action=OptionalOrFalse,
|
action=OptionalOrFalse,
|
||||||
default=v["default"],
|
default=v["default"],
|
||||||
help=v["help"],
|
help=v["help"],
|
||||||
|
@ -109,7 +110,7 @@ def main():
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
v["arg"],
|
*v["args"],
|
||||||
type=v["type"],
|
type=v["type"],
|
||||||
choices=v["choices"],
|
choices=v["choices"],
|
||||||
default=None,
|
default=None,
|
||||||
|
@ -117,7 +118,7 @@ def main():
|
||||||
)
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
v["arg"],
|
*v["args"],
|
||||||
type=v["type"],
|
type=v["type"],
|
||||||
default=None,
|
default=None,
|
||||||
help=v["help"],
|
help=v["help"],
|
||||||
|
|
|
@ -19,7 +19,7 @@ from zotify.config import Config
|
||||||
from zotify.file import TranscodingError
|
from zotify.file import TranscodingError
|
||||||
from zotify.loader import Loader
|
from zotify.loader import Loader
|
||||||
from zotify.printer import PrintChannel, Printer
|
from zotify.printer import PrintChannel, Printer
|
||||||
from zotify.utils import API_URL, AudioFormat, b62_to_hex
|
from zotify.utils import API_URL, AudioFormat, MetadataEntry, b62_to_hex
|
||||||
|
|
||||||
|
|
||||||
class ParseError(ValueError):
|
class ParseError(ValueError):
|
||||||
|
@ -36,7 +36,7 @@ class PlayableData(NamedTuple):
|
||||||
id: PlayableId
|
id: PlayableId
|
||||||
library: Path
|
library: Path
|
||||||
output: str
|
output: str
|
||||||
metadata: dict[str, Any] = {}
|
metadata: list[MetadataEntry] = []
|
||||||
|
|
||||||
|
|
||||||
class Selection:
|
class Selection:
|
||||||
|
@ -385,7 +385,7 @@ class App:
|
||||||
self.__config.chunk_size,
|
self.__config.chunk_size,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.__config.save_lyrics_file and playable.type == PlayableType.TRACK:
|
if playable.type == PlayableType.TRACK and self.__config.lyrics_file:
|
||||||
with Loader("Fetching lyrics..."):
|
with Loader("Fetching lyrics..."):
|
||||||
try:
|
try:
|
||||||
track.get_lyrics().save(output)
|
track.get_lyrics().save(output)
|
||||||
|
|
|
@ -17,6 +17,7 @@ DOWNLOAD_QUALITY = "download_quality"
|
||||||
FFMPEG_ARGS = "ffmpeg_args"
|
FFMPEG_ARGS = "ffmpeg_args"
|
||||||
FFMPEG_PATH = "ffmpeg_path"
|
FFMPEG_PATH = "ffmpeg_path"
|
||||||
LANGUAGE = "language"
|
LANGUAGE = "language"
|
||||||
|
LYRICS_FILE = "lyrics_file"
|
||||||
LYRICS_ONLY = "lyrics_only"
|
LYRICS_ONLY = "lyrics_only"
|
||||||
MUSIC_LIBRARY = "music_library"
|
MUSIC_LIBRARY = "music_library"
|
||||||
OUTPUT = "output"
|
OUTPUT = "output"
|
||||||
|
@ -34,7 +35,6 @@ PRINT_PROGRESS = "print_progress"
|
||||||
PRINT_SKIPS = "print_skips"
|
PRINT_SKIPS = "print_skips"
|
||||||
PRINT_WARNINGS = "print_warnings"
|
PRINT_WARNINGS = "print_warnings"
|
||||||
REPLACE_EXISTING = "replace_existing"
|
REPLACE_EXISTING = "replace_existing"
|
||||||
SAVE_LYRICS_FILE = "save_lyrics_file"
|
|
||||||
SAVE_METADATA = "save_metadata"
|
SAVE_METADATA = "save_metadata"
|
||||||
SAVE_SUBTITLES = "save_subtitles"
|
SAVE_SUBTITLES = "save_subtitles"
|
||||||
SKIP_DUPLICATES = "skip_duplicates"
|
SKIP_DUPLICATES = "skip_duplicates"
|
||||||
|
@ -72,190 +72,190 @@ CONFIG_VALUES = {
|
||||||
CREDENTIALS: {
|
CREDENTIALS: {
|
||||||
"default": CONFIG_PATHS["creds"],
|
"default": CONFIG_PATHS["creds"],
|
||||||
"type": Path,
|
"type": Path,
|
||||||
"arg": "--credentials",
|
"args": ["--credentials"],
|
||||||
"help": "Path to credentials file",
|
"help": "Path to credentials file",
|
||||||
},
|
},
|
||||||
PATH_ARCHIVE: {
|
PATH_ARCHIVE: {
|
||||||
"default": CONFIG_PATHS["archive"],
|
"default": CONFIG_PATHS["archive"],
|
||||||
"type": Path,
|
"type": Path,
|
||||||
"arg": "--archive",
|
"args": ["--archive"],
|
||||||
"help": "Path to track archive file",
|
"help": "Path to track archive file",
|
||||||
},
|
},
|
||||||
MUSIC_LIBRARY: {
|
MUSIC_LIBRARY: {
|
||||||
"default": LIBRARY_PATHS["music"],
|
"default": LIBRARY_PATHS["music"],
|
||||||
"type": Path,
|
"type": Path,
|
||||||
"arg": "--music-library",
|
"args": ["--music-library"],
|
||||||
"help": "Path to root of music library",
|
"help": "Path to root of music library",
|
||||||
},
|
},
|
||||||
PODCAST_LIBRARY: {
|
PODCAST_LIBRARY: {
|
||||||
"default": LIBRARY_PATHS["podcast"],
|
"default": LIBRARY_PATHS["podcast"],
|
||||||
"type": Path,
|
"type": Path,
|
||||||
"arg": "--podcast-library",
|
"args": ["--podcast-library"],
|
||||||
"help": "Path to root of podcast library",
|
"help": "Path to root of podcast library",
|
||||||
},
|
},
|
||||||
PLAYLIST_LIBRARY: {
|
PLAYLIST_LIBRARY: {
|
||||||
"default": LIBRARY_PATHS["playlist"],
|
"default": LIBRARY_PATHS["playlist"],
|
||||||
"type": Path,
|
"type": Path,
|
||||||
"arg": "--playlist-library",
|
"args": ["--playlist-library"],
|
||||||
"help": "Path to root of playlist library",
|
"help": "Path to root of playlist library",
|
||||||
},
|
},
|
||||||
OUTPUT_ALBUM: {
|
OUTPUT_ALBUM: {
|
||||||
"default": OUTPUT_PATHS["album"],
|
"default": OUTPUT_PATHS["album"],
|
||||||
"type": str,
|
"type": str,
|
||||||
"arg": "--output-album",
|
"args": ["--output-album", "-oa"],
|
||||||
"help": "File layout for saved albums",
|
"help": "File layout for saved albums",
|
||||||
},
|
},
|
||||||
OUTPUT_PLAYLIST_TRACK: {
|
OUTPUT_PLAYLIST_TRACK: {
|
||||||
"default": OUTPUT_PATHS["playlist_track"],
|
"default": OUTPUT_PATHS["playlist_track"],
|
||||||
"type": str,
|
"type": str,
|
||||||
"arg": "--output-playlist-track",
|
"args": ["--output-playlist-track", "-opt"],
|
||||||
"help": "File layout for tracks in a playlist",
|
"help": "File layout for tracks in a playlist",
|
||||||
},
|
},
|
||||||
OUTPUT_PLAYLIST_EPISODE: {
|
OUTPUT_PLAYLIST_EPISODE: {
|
||||||
"default": OUTPUT_PATHS["playlist_episode"],
|
"default": OUTPUT_PATHS["playlist_episode"],
|
||||||
"type": str,
|
"type": str,
|
||||||
"arg": "--output-playlist-episode",
|
"args": ["--output-playlist-episode", "-ope"],
|
||||||
"help": "File layout for episodes in a playlist",
|
"help": "File layout for episodes in a playlist",
|
||||||
},
|
},
|
||||||
OUTPUT_PODCAST: {
|
OUTPUT_PODCAST: {
|
||||||
"default": OUTPUT_PATHS["podcast"],
|
"default": OUTPUT_PATHS["podcast"],
|
||||||
"type": str,
|
"type": str,
|
||||||
"arg": "--output-podcast",
|
"args": ["--output-podcast", "-op"],
|
||||||
"help": "File layout for saved podcasts",
|
"help": "File layout for saved podcasts",
|
||||||
},
|
},
|
||||||
DOWNLOAD_QUALITY: {
|
DOWNLOAD_QUALITY: {
|
||||||
"default": "auto",
|
"default": "auto",
|
||||||
"type": Quality.from_string,
|
"type": Quality.from_string,
|
||||||
"choices": list(Quality),
|
"choices": list(Quality),
|
||||||
"arg": "--download-quality",
|
"args": ["--download-quality"],
|
||||||
"help": "Audio download quality (auto for highest available)",
|
"help": "Audio download quality (auto for highest available)",
|
||||||
},
|
},
|
||||||
ARTWORK_SIZE: {
|
ARTWORK_SIZE: {
|
||||||
"default": "large",
|
"default": "large",
|
||||||
"type": ImageSize.from_string,
|
"type": ImageSize.from_string,
|
||||||
"choices": list(ImageSize),
|
"choices": list(ImageSize),
|
||||||
"arg": "--artwork-size",
|
"args": ["--artwork-size"],
|
||||||
"help": "Image size of track's cover art",
|
"help": "Image size of track's cover art",
|
||||||
},
|
},
|
||||||
AUDIO_FORMAT: {
|
AUDIO_FORMAT: {
|
||||||
"default": "vorbis",
|
"default": "vorbis",
|
||||||
"type": AudioFormat,
|
"type": AudioFormat,
|
||||||
"choices": [n.value.name for n in AudioFormat],
|
"choices": [n.value.name for n in AudioFormat],
|
||||||
"arg": "--audio-format",
|
"args": ["--audio-format"],
|
||||||
"help": "Audio format of final track output",
|
"help": "Audio format of final track output",
|
||||||
},
|
},
|
||||||
TRANSCODE_BITRATE: {
|
TRANSCODE_BITRATE: {
|
||||||
"default": -1,
|
"default": -1,
|
||||||
"type": int,
|
"type": int,
|
||||||
"arg": "--bitrate",
|
"args": ["--bitrate"],
|
||||||
"help": "Transcoding bitrate (-1 to use download rate)",
|
"help": "Transcoding bitrate (-1 to use download rate)",
|
||||||
},
|
},
|
||||||
FFMPEG_PATH: {
|
FFMPEG_PATH: {
|
||||||
"default": "",
|
"default": "",
|
||||||
"type": str,
|
"type": str,
|
||||||
"arg": "--ffmpeg-path",
|
"args": ["--ffmpeg-path"],
|
||||||
"help": "Path to ffmpeg binary",
|
"help": "Path to ffmpeg binary",
|
||||||
},
|
},
|
||||||
FFMPEG_ARGS: {
|
FFMPEG_ARGS: {
|
||||||
"default": "",
|
"default": "",
|
||||||
"type": str,
|
"type": str,
|
||||||
"arg": "--ffmpeg-args",
|
"args": ["--ffmpeg-args"],
|
||||||
"help": "Additional ffmpeg arguments when transcoding",
|
"help": "Additional ffmpeg arguments when transcoding",
|
||||||
},
|
},
|
||||||
SAVE_SUBTITLES: {
|
SAVE_SUBTITLES: {
|
||||||
"default": False,
|
"default": False,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--save-subtitles",
|
"args": ["--save-subtitles"],
|
||||||
"help": "Save subtitles from podcasts to a .srt file",
|
"help": "Save subtitles from podcasts to a .srt file",
|
||||||
},
|
},
|
||||||
LANGUAGE: {
|
LANGUAGE: {
|
||||||
"default": "en",
|
"default": "en",
|
||||||
"type": str,
|
"type": str,
|
||||||
"arg": "--language",
|
"args": ["--language"],
|
||||||
"help": "Language for metadata",
|
"help": "Language for metadata",
|
||||||
},
|
},
|
||||||
SAVE_LYRICS_FILE: {
|
LYRICS_FILE: {
|
||||||
"default": True,
|
"default": False,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--save-lyrics-file",
|
"args": ["--lyrics-file"],
|
||||||
"help": "Save lyrics to a file",
|
"help": "Save lyrics to a file",
|
||||||
},
|
},
|
||||||
LYRICS_ONLY: {
|
LYRICS_ONLY: {
|
||||||
"default": False,
|
"default": False,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--lyrics-only",
|
"args": ["--lyrics-only"],
|
||||||
"help": "Only download lyrics and not actual audio",
|
"help": "Only download lyrics and not actual audio",
|
||||||
},
|
},
|
||||||
CREATE_PLAYLIST_FILE: {
|
CREATE_PLAYLIST_FILE: {
|
||||||
"default": True,
|
"default": True,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--playlist-file",
|
"args": ["--playlist-file"],
|
||||||
"help": "Save playlist information to an m3u8 file",
|
"help": "Save playlist information to an m3u8 file",
|
||||||
},
|
},
|
||||||
SAVE_METADATA: {
|
SAVE_METADATA: {
|
||||||
"default": True,
|
"default": True,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--save-metadata",
|
"args": ["--save-metadata"],
|
||||||
"help": "Save metadata, required for other metadata options",
|
"help": "Save metadata, required for other metadata options",
|
||||||
},
|
},
|
||||||
ALL_ARTISTS: {
|
ALL_ARTISTS: {
|
||||||
"default": True,
|
"default": True,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--all-artists",
|
"args": ["--all-artists"],
|
||||||
"help": "Add all track artists to artist tag in metadata",
|
"help": "Add all track artists to artist tag in metadata",
|
||||||
},
|
},
|
||||||
REPLACE_EXISTING: {
|
REPLACE_EXISTING: {
|
||||||
"default": False,
|
"default": False,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--replace-existing",
|
"args": ["--replace-existing"],
|
||||||
"help": "Overwrite existing files with the same name",
|
"help": "Overwrite existing files with the same name",
|
||||||
},
|
},
|
||||||
SKIP_PREVIOUS: {
|
SKIP_PREVIOUS: {
|
||||||
"default": True,
|
"default": True,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--skip-previous",
|
"args": ["--skip-previous"],
|
||||||
"help": "Skip previously downloaded songs",
|
"help": "Skip previously downloaded songs",
|
||||||
},
|
},
|
||||||
SKIP_DUPLICATES: {
|
SKIP_DUPLICATES: {
|
||||||
"default": True,
|
"default": True,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--skip-duplicates",
|
"args": ["--skip-duplicates"],
|
||||||
"help": "Skip downloading existing track to different album",
|
"help": "Skip downloading existing track to different album",
|
||||||
},
|
},
|
||||||
CHUNK_SIZE: {
|
CHUNK_SIZE: {
|
||||||
"default": 131072,
|
"default": 16384,
|
||||||
"type": int,
|
"type": int,
|
||||||
"arg": "--chunk-size",
|
"args": ["--chunk-size"],
|
||||||
"help": "Number of bytes read at a time during download",
|
"help": "Number of bytes read at a time during download",
|
||||||
},
|
},
|
||||||
PRINT_DOWNLOADS: {
|
PRINT_DOWNLOADS: {
|
||||||
"default": False,
|
"default": False,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--print-downloads",
|
"args": ["--print-downloads"],
|
||||||
"help": "Print messages when a song is finished downloading",
|
"help": "Print messages when a song is finished downloading",
|
||||||
},
|
},
|
||||||
PRINT_PROGRESS: {
|
PRINT_PROGRESS: {
|
||||||
"default": True,
|
"default": True,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--print-progress",
|
"args": ["--print-progress"],
|
||||||
"help": "Show progress bars",
|
"help": "Show progress bars",
|
||||||
},
|
},
|
||||||
PRINT_SKIPS: {
|
PRINT_SKIPS: {
|
||||||
"default": False,
|
"default": False,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--print-skips",
|
"args": ["--print-skips"],
|
||||||
"help": "Show messages if a song is being skipped",
|
"help": "Show messages if a song is being skipped",
|
||||||
},
|
},
|
||||||
PRINT_WARNINGS: {
|
PRINT_WARNINGS: {
|
||||||
"default": True,
|
"default": True,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--print-warnings",
|
"args": ["--print-warnings"],
|
||||||
"help": "Show warnings",
|
"help": "Show warnings",
|
||||||
},
|
},
|
||||||
PRINT_ERRORS: {
|
PRINT_ERRORS: {
|
||||||
"default": True,
|
"default": True,
|
||||||
"type": bool,
|
"type": bool,
|
||||||
"arg": "--print-errors",
|
"args": ["--print-errors"],
|
||||||
"help": "Show errors",
|
"help": "Show errors",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -272,6 +272,7 @@ class Config:
|
||||||
ffmpeg_path: str
|
ffmpeg_path: str
|
||||||
music_library: Path
|
music_library: Path
|
||||||
language: str
|
language: str
|
||||||
|
lyrics_file: bool
|
||||||
output_album: str
|
output_album: str
|
||||||
output_liked: str
|
output_liked: str
|
||||||
output_podcast: str
|
output_podcast: str
|
||||||
|
@ -280,7 +281,6 @@ class Config:
|
||||||
playlist_library: Path
|
playlist_library: Path
|
||||||
podcast_library: Path
|
podcast_library: Path
|
||||||
print_progress: bool
|
print_progress: bool
|
||||||
save_lyrics_file: bool
|
|
||||||
save_metadata: bool
|
save_metadata: bool
|
||||||
transcode_bitrate: int
|
transcode_bitrate: int
|
||||||
|
|
||||||
|
@ -303,6 +303,8 @@ class Config:
|
||||||
jsonvalues[key] = str(CONFIG_VALUES[key]["default"])
|
jsonvalues[key] = str(CONFIG_VALUES[key]["default"])
|
||||||
with open(self.__config_file, "w+", encoding="utf-8") as conf:
|
with open(self.__config_file, "w+", encoding="utf-8") as conf:
|
||||||
dump(jsonvalues, conf, indent=4)
|
dump(jsonvalues, conf, indent=4)
|
||||||
|
else:
|
||||||
|
self.__config_file = None
|
||||||
|
|
||||||
for key in CONFIG_VALUES:
|
for key in CONFIG_VALUES:
|
||||||
# Override config with commandline arguments
|
# Override config with commandline arguments
|
||||||
|
@ -318,10 +320,14 @@ class Config:
|
||||||
key,
|
key,
|
||||||
self.__parse_arg_value(key, CONFIG_VALUES[key]["default"]),
|
self.__parse_arg_value(key, CONFIG_VALUES[key]["default"]),
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
self.__config_file = None
|
|
||||||
|
|
||||||
# Make "output" arg override all output_* options
|
# "library" arg overrides all *_library options
|
||||||
|
if args.library:
|
||||||
|
self.music_library = args.library
|
||||||
|
self.playlist_library = args.library
|
||||||
|
self.podcast_library = args.library
|
||||||
|
|
||||||
|
# "output" arg overrides all output_* options
|
||||||
if args.output:
|
if args.output:
|
||||||
self.output_album = args.output
|
self.output_album = args.output
|
||||||
self.output_liked = args.output
|
self.output_liked = args.output
|
||||||
|
|
|
@ -63,7 +63,7 @@ class Printer:
|
||||||
iterable=iterable,
|
iterable=iterable,
|
||||||
desc=desc,
|
desc=desc,
|
||||||
total=total,
|
total=total,
|
||||||
disable=False, # cls.__config.print_progress,
|
disable=not cls.__config.print_progress,
|
||||||
leave=leave,
|
leave=leave,
|
||||||
position=position,
|
position=position,
|
||||||
unit=unit,
|
unit=unit,
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from argparse import Action, ArgumentError
|
from argparse import Action, ArgumentError, HelpFormatter
|
||||||
from enum import Enum, IntEnum
|
from enum import Enum, IntEnum
|
||||||
from re import IGNORECASE, sub
|
from re import IGNORECASE, sub
|
||||||
|
from sys import exit
|
||||||
from sys import platform as PLATFORM
|
from sys import platform as PLATFORM
|
||||||
|
from sys import stderr
|
||||||
from typing import Any, NamedTuple
|
from typing import Any, NamedTuple
|
||||||
|
|
||||||
from librespot.audio.decoders import AudioQuality
|
from librespot.audio.decoders import AudioQuality
|
||||||
|
@ -15,8 +17,8 @@ BASE62 = Base62.create_instance_with_inverted_character_set()
|
||||||
|
|
||||||
|
|
||||||
class AudioCodec(NamedTuple):
|
class AudioCodec(NamedTuple):
|
||||||
ext: str
|
|
||||||
name: str
|
name: str
|
||||||
|
ext: str
|
||||||
|
|
||||||
|
|
||||||
class AudioFormat(Enum):
|
class AudioFormat(Enum):
|
||||||
|
@ -69,6 +71,43 @@ class ImageSize(IntEnum):
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataEntry:
|
||||||
|
name: str
|
||||||
|
value: Any
|
||||||
|
output: str
|
||||||
|
|
||||||
|
def __init__(self, name: str, value: Any, output_value: str | None = None):
|
||||||
|
"""
|
||||||
|
Holds metadata entries
|
||||||
|
args:
|
||||||
|
name: name of metadata key
|
||||||
|
value: Value to use in metadata tags
|
||||||
|
output_value: Value when used in output formatting, if none is provided
|
||||||
|
will use value from previous argument.
|
||||||
|
"""
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
if type(value) == list:
|
||||||
|
value = "\0".join(value)
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
if output_value is None:
|
||||||
|
output_value = self.value
|
||||||
|
elif output_value == "":
|
||||||
|
output_value = None
|
||||||
|
if type(output_value) == list:
|
||||||
|
output_value = ", ".join(output_value)
|
||||||
|
self.output = str(output_value)
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleHelpFormatter(HelpFormatter):
|
||||||
|
def _format_usage(self, usage, actions, groups, prefix):
|
||||||
|
if usage is not None:
|
||||||
|
super()._format_usage(usage, actions, groups, prefix)
|
||||||
|
stderr.write('zotify: error: unrecognized arguments - try "zotify -h"\n')
|
||||||
|
exit(2)
|
||||||
|
|
||||||
|
|
||||||
class OptionalOrFalse(Action):
|
class OptionalOrFalse(Action):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -103,38 +142,15 @@ class OptionalOrFalse(Action):
|
||||||
)
|
)
|
||||||
|
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
if values is None and not option_string.startswith("--no-"):
|
if values is not None:
|
||||||
raise ArgumentError(self, "expected 1 argument")
|
raise ArgumentError(self, "expected 0 arguments")
|
||||||
setattr(
|
setattr(
|
||||||
namespace,
|
namespace,
|
||||||
self.dest,
|
self.dest,
|
||||||
values if not option_string.startswith("--no-") else False,
|
True if not option_string.startswith("--no-") else False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MetadataEntry:
|
|
||||||
def __init__(self, name: str, value: Any, output_value: str | None = None):
|
|
||||||
"""
|
|
||||||
Holds metadata entries
|
|
||||||
args:
|
|
||||||
name: name of metadata key
|
|
||||||
tag_val: Value to use in metadata tags
|
|
||||||
output_value: Value when used in output formatting
|
|
||||||
"""
|
|
||||||
self.name = name
|
|
||||||
if type(value) == list:
|
|
||||||
value = "\0".join(value)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
if output_value is None:
|
|
||||||
output_value = value
|
|
||||||
if output_value == "":
|
|
||||||
output_value = None
|
|
||||||
if type(output_value) == list:
|
|
||||||
output_value = ", ".join(output_value)
|
|
||||||
self.output = str(output_value)
|
|
||||||
|
|
||||||
|
|
||||||
def fix_filename(filename: str, substitute: str = "_", platform: str = PLATFORM) -> str:
|
def fix_filename(filename: str, substitute: str = "_", platform: str = PLATFORM) -> str:
|
||||||
"""
|
"""
|
||||||
Replace invalid characters on Linux/Windows/MacOS with underscores.
|
Replace invalid characters on Linux/Windows/MacOS with underscores.
|
||||||
|
|
Loading…
Reference in New Issue