feat: download tracks with full ID3 metadata
- Add downloads.py: "Artist - Title.mp3" naming, no-overwrite dedupe, configurable download_dir, progress and per-item callbacks. - Save maximum metadata via tags.py (ID3v2.3, mutagen): artists, album and album artist, track/disc, year/date, raw genre, label, duration, explicit flag, cover 600x600, plain and synced lyrics (MTS then LRCLIB), track URLs and Yandex/MTS ids. - TUI/Qt: download current track and queue from the menu, "d" key in lists, folder picker in settings; CLI download gains -d/--dir and --no-tags. - Add mutagen to requirements and document download/metadata in README. - Fix MPRIS update_metadata crashing without dbus-next.
This commit is contained in:
63
mtsmusic.py
63
mtsmusic.py
@@ -872,34 +872,33 @@ def cmd_play(args: argparse.Namespace) -> None:
|
||||
|
||||
|
||||
def cmd_download(args: argparse.Namespace) -> None:
|
||||
api = Api()
|
||||
print_track_info(api, args.track_id)
|
||||
url, _ = resolve_stream(api, args.track_id)
|
||||
force_ipv4()
|
||||
output = Path(args.output or f"{args.track_id}.mp3")
|
||||
from downloads import download_track, stream_to_file, tag_downloaded
|
||||
from settings import get_setting
|
||||
|
||||
with requests.get(
|
||||
url,
|
||||
stream=True,
|
||||
headers={"User-Agent": USER_AGENT},
|
||||
timeout=60,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
total = int(response.headers.get("Content-Length") or 0)
|
||||
done = 0
|
||||
with output.open("wb") as handle:
|
||||
for chunk in response.iter_content(chunk_size=256 * 1024):
|
||||
if not chunk:
|
||||
continue
|
||||
handle.write(chunk)
|
||||
done += len(chunk)
|
||||
if total:
|
||||
percent = done * 100 // total
|
||||
kib = done // 1024
|
||||
print(
|
||||
f"\r{percent:3d}% {kib} KiB",
|
||||
end="",
|
||||
file=sys.stderr)
|
||||
api = Api()
|
||||
track = get_track(api, args.track_id)
|
||||
print(fmt_track(track), file=sys.stderr)
|
||||
|
||||
def progress(done: int, total: int) -> None:
|
||||
if total:
|
||||
percent = done * 100 // total
|
||||
print(
|
||||
f"\r{percent:3d}% {done // 1024} KiB",
|
||||
end="",
|
||||
file=sys.stderr)
|
||||
|
||||
with_tags = not args.no_tags
|
||||
if args.output:
|
||||
# Явный путь: скачиваем ровно в указанный файл, как раньше.
|
||||
url, _ = resolve_stream(api, args.track_id)
|
||||
output = stream_to_file(url, Path(args.output), progress)
|
||||
if with_tags:
|
||||
tag_downloaded(api, track, output)
|
||||
else:
|
||||
dest = args.dir or get_setting("download_dir")
|
||||
output = download_track(api, track, dest, progress, with_tags)
|
||||
|
||||
if sys.stderr.isatty():
|
||||
print(file=sys.stderr)
|
||||
print(f"Сохранено: {output} ({output.stat().st_size} байт)")
|
||||
|
||||
@@ -1145,6 +1144,16 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
download = sub.add_parser("download", help="скачать трек в MP3")
|
||||
download.add_argument("track_id")
|
||||
download.add_argument("-o", "--output", help="имя файла")
|
||||
download.add_argument(
|
||||
"-d",
|
||||
"--dir",
|
||||
help="папка для сохранения (по умолчанию из download_dir)",
|
||||
)
|
||||
download.add_argument(
|
||||
"--no-tags",
|
||||
action="store_true",
|
||||
help="не записывать ID3-теги (текст, обложка, метаданные)",
|
||||
)
|
||||
download.set_defaults(func=cmd_download)
|
||||
|
||||
return parser
|
||||
|
||||
Reference in New Issue
Block a user